FYI
Portions of this post was edited for clarity with the help of
ChatGPT.
The Frigate NVR project is a relatively new entrant to the home security camera DVR space.
Like most immature yet popular software, it has a killer feature - very good object detection that just works™ and robust Home Assistant integration.
Unfortunately, the Frigate docs are a bit spartan particularly around installing; they more or less start with “now that you’ve installed it, let’s go over configuring / using …”.
While this does seem like a rather import omission, it’s somewhat intentional.
This is because Frigate is only distributed as a Docker container so installing really boils down to “get a computer that runs docker
and then make sure docker run ...
is executed when you want”.
Most of the existing guides out there all use docker-compose
with only some minor attention paid to supervisory configuration:
This document isn’t going to introduce anything new or innovative but should offer an alternative that closely tracks the way I did it.
Goal
This post is going to cover the steps taken to get a host that:
- runs the Frigate docker container via
systemd
- uses a coral.ai edge TPU for accelerated object detection
- records footage on a network mounted file share
This post is not going to cover details that are likely specific to your deployment or are whole posts on their own:
- host or camera hardware selection.
- camera placement
- host setup tasks like installing OS
- configuring frigate to use your specific cameras
- integrating frigate with Home Assistant
Prep
I’m going to assume that you’ve already got a suitable host for running Frigate and that you have already set it up as to your liking; ssh
keys, $hostname
set, timezone and ntp servers set up … etc.
I used a modern Intel N6005 system running Ubuntu 22.04 but the general process should be very similar for you and may even be identical if you use a debian based OS on similar hardware.
Install Docker
Installing the bare docker runtime is pretty straight forward. As this is a debian based host, I followed the apt
repo method.
Make sure the system is up to date before installing anything new - if something goes wrong and breaks your system you’ll have an easier time figuring out what needs fixing.
1
2
3
4
5
6
7
8
| karl@nvr:~$ sudo -i
[sudo] password for karl:
root@nvr:~# apt update; apt dist-upgrade -y; apt autoremove -y; apt autoclean -y
<...>
root@nvr:~# cat /var/run/reboot-required.pkgs
linux-image-5.19.0-35-generic
linux-base
root@nvr:~# reboot
|
Post update, install a subset of the docker packages:
1
2
3
4
5
6
7
8
9
10
| karl@nvr:~$ sudo mkdir -m 0755 -p /etc/apt/keyrings
karl@nvr:~$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
karl@nvr:~$ echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
karl@nvr:~$ sudo apt update; sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin
<...>
After this operation, 401 MB of additional disk space will be used.
Do you want to continue? [Y/n] Y
<...>
|
And then check signs of life:
1
2
3
4
5
6
7
| karl@nvr:~$ sudo docker run hello-world
<...>
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
<...>
|
With that, docker is good to go and we can move on to the other prerequisites.
Create Frigate User
It’s good practice to create dedicated users with limited permissions to run the service(s) under.
Create the user and verify that the user does not need elevated credentials to talk to docker:
1
2
3
4
5
6
7
8
9
| karl@nvr:~$ sudo useradd --comment "service user for Frigate NVR" --groups docker --system --shell /usr/bin/bash frigate
karl@nvr:~$ sudo -i
root@nvr:~# su - frigate
su: warning: cannot change directory to /home/frigate: No such file or directory
frigate@nvr:/root$ groups
frigate docker
frigate@nvr:/root$ docker container list -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dd7a5d1694c7 hello-world "/hello" 4 minutes ago Exited (0) 4 minutes ago mystifying_yalow
|
Quite note about docker security
Yes, I am aware that - because the frigate
user can invoke docker
commands directly - it isn’t difficult for the frigate
user to escalate credentials to those of root
.
Running the docker daemon in rootless mode or an alternative ’non-root’ container management tool is one way to eliminate this risk but is beyond the scope of this post.
As always, defense in depth; this frigate host is appropriately firewalled off from the rest of the network.
After docker
is installed and the frigate
user is added to the docker
group, the next pre-requisite is storage for Frigate recordings.
Create Mounts
Tip
You can skip this step if you do not wish to use remote storage for the Frigate configuration and recordings.
Storage quotas
Frigate does not have sophisticated controls for configuring how long recordings are kept so you are encouraged to set up a storage quota for whatever disk/mount/share you use for network recordings.
If you are going the network share route, the software on the NAS likely has this functionality.
If you are going with local storage, the simplest way to enforce a quota is to use a dedicated partition.
Using .mount
files, it is trivial to have systemd mount the network share before starting Frigate.
I chose to use a NFS share as both the NAS and the Frigate host are *NIX based and file system permissions tend to work a lot cleaner over NFS compared to Samba.
If you run into errors related to the database that frigate uses, you may consider re-locating the database to a local mount.
The technique outlined below will work for Samba shares as well but the .mount
files will be configured slightly differently and you’ll need to install slightly different software:
1
2
3
4
| karl@nvr:~$ sudo apt install nfs-common
# Only needed if you use SMB shares. Without this package, you will likely get obscure errors related to hostname resolution
# See: https://askubuntu.com/questions/373340/ubuntu-server-13-10-cant-mount-hard-drive-that-is-on-my-router/374699#374699
karl@nvr:~$ sudo apt-get install cifs-utils
|
With the correct smb/nfs packages installed, tell systemd how to mount the network share locally automatically:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # Create path on host
karl@nvr:~$ sudo mkdir -p /mnt/frigate
karl@nvr:~$ sudo chown -R frigate /mnt/frigate
karl@nvr:~$ ls -lah /mnt/frigate/
total 8.0K
drwxr-xr-x 2 frigate root 4.0K Mar 2 08:37 .
drwxr-xr-x 3 root root 4.0K Mar 2 08:37 ..
# Create the mount files
karl@nvr:~$ sudo $EDITOR /etc/systemd/system/mnt-frigate.mount
karl@nvr:~$ sudo $EDITOR /etc/systemd/system/mnt-frigate.automount
# And apply them
karl@nvr:~$ sudo systemctl daemon-reload
karl@nvr:~$ sudo systemctl enable mnt-frigate.moun
karl@nvr:~$ sudo systemctl enable mnt-frigate.automount
karl@nvr:~$ sudo systemctl start mnt-frigate.mount
karl@nvr:~$ sudo systemctl start mnt-frigate.automount
|
The /etc/systemd/system/mnt-frigate.mount
file looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| [Unit]
Description=Mount Frigate NFS share share locally for frigate docker container
Requires=systemd-networkd.service
After=network-online.target
Wants=network-online.target
[Mount]
What=yourServerIPHere:/path/to/your/nfs/share/here
Where=/mnt/frigate
Type=nfs
Options=default
TimeoutSec=5
[Install]
WantedBy=multi-user.target
|
And the /etc/systemd/system/mnt-frigate.automount
looks like this:
1
2
3
4
5
6
7
8
9
| [Unit]
Description=automount for frigate
[Automount]
Where=/mnt/frigate
TimeoutIdleSec=0
[Install]
WantedBy=multi-user.target
|
After systemctl start/enable ...
the mounts should be up and running.
Use systemctl status
to check that things worked properly and use journalctl
to check logs if things went wrong.
Ideally you’ll have something that looks like this:
1
2
3
4
5
6
7
| root@nvr:~# systemctl status mnt-frigate.mount
● mnt-frigate.mount - Mount Frigate NFS share share locally for frigate docker container
Loaded: loaded (/proc/self/mountinfo; enabled; preset: enabled)
Active: active (mounted) since <...>
TriggeredBy: ● mnt-frigate.automount
Where: /mnt/frigate
<...>
|
At this point, all the basic pre-requisites are satisfied: a frigate
specific user can run docker run ...
commands and systemd
will automatically mount the network share locally.
Coral.ai edge TPU
Tip
Skip this step if you are not using PCI-E based edge TPU nodes.
As mentioned at the top, one of the features that makes Frigate so attractive is how easy it is to use dedicated hardware for image/object classification.
I am using a PCI-Express based TPU so there’s a little bit more wor required to successfully pass a PCIe device into a docker container.
Fortunately this process is a lot simpler than it used to be and the google provided instructions are pretty clear:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # Confirm there is no apex driver present already.
karl@nvr:~$ sudo lsmod | grep apex
# Add apt repos
karl@nvr:~$ echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | sudo tee /etc/apt/sources.list.d/coral-edgetpu.list
deb https://packages.cloud.google.com/apt coral-edgetpu-stable main
# Install the driver
karl@nvr:~$ curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
<...>
karl@nvr:~$ sudo apt-get update; sudo apt-get install gasket-dkms libedgetpu1-std
Get:1 https://packages.cloud.google.com/apt coral-edgetpu-stable InRelease [6,332 B]
<...>
The following additional packages will be installed:
build-essential bzip2 cpp cpp-12 dctrl-tools dh-dkms dkms dpkg-dev fakeroot fontconfig-config fonts-dejavu-core g++ g++-12 gcc
<...>
Do you want to continue? [Y/n] Y
<...>
|
After install, udev
rules are needed to make sure the proper driver is loaded:
1
2
3
4
5
6
7
8
9
10
| # Confirm file does not exist
karl@nvr:~$ sudo cat /etc/udev/rules.d/65-apex.rules
cat: /etc/udev/rules.d/65-apex.rules: No such file or directory
karl@nvr:~$ sudo sh -c "echo 'SUBSYSTEM==\"apex\", MODE=\"0660\", GROUP=\"apex\"' >> /etc/udev/rules.d/65-apex.rules"
# Add the `frigate` user to the `apex` group so it can access the "virtual" PCIe devices
karl@nvr:~$ sudo groupadd -U frigate apex
karl@nvr:~$ sudo groups frigate
frigate : frigate docker apex
# Cleanest way to make sure driver and udev rules work is to reboot
karl@nvr:~$ sudo reboot
|
Checking that the apex
driver is properly loaded is quick and painless:
1
2
3
4
5
6
7
| # I have a "dual" TPU so two devices show up. If you only have a "single" TPU, only one will show up.
karl@nvr:~$ sudo lspci -nn | grep 089a
[sudo] password for karl:
03:00.0 System peripheral [0880]: Global Unichip Corp. Coral Edge TPU [1ac1:089a]
04:00.0 System peripheral [0880]: Global Unichip Corp. Coral Edge TPU [1ac1:089a]
karl@nvr:~$ sudo ls /dev/apex_*
/dev/apex_0 /dev/apex_1
|
And with that, all the core/extended pre-requisites are done and the remaining work is actually pretty minimal.
Install
After the pre-requisites are satisfied so all that’s left is the .unit
file which will wrap the docker run
commands.
EnvironmentFile
Frigate supports env-var substitution in it’s configuration file like so:
1
2
3
| mqtt:
host: mqtt.server.com
user: {FRIGATE_SOME_KEY_HERE}
|
The configuration file - sans sensitive information - can now be safely checked in to source control.
Create a file just for holding our env-vars:
1
2
3
4
5
6
7
8
9
10
| karl@nvr:~$ sudo mkdir -p /etc/frigate
[sudo] password for karl:
karl@nvr:~$ sudo touch /etc/frigate/secrets
karl@nvr:~$ sudo chown -R frigate:frigate /etc/frigate
karl@nvr:~$ sudo chmod -R 0750 /etc/frigate
karl@nvr:~$ sudo ls -lah /etc/frigate/
total 12K
drwxr-x--- 2 frigate frigate 4.0K Mar 4 16:13 .
drwxr-xr-x 110 root root 4.0K Mar 4 15:16 ..
-rwxr-x--- 1 frigate frigate 356 Mar 4 16:13 secrets
|
The secrets file is simple key=value format:
1
2
3
4
5
6
7
| karl@nvr:~$ sudo cat /etc/frigate/secrets
# Note: frigate < 0.12 does not support env-var for mqtt.user; set it here/now for use in the future.
FRIGATE_MQTT_USER=frigate
FRIGATE_MQTT_PASSWORD=ChangeMe
FRIGATE_CAM01_RTSP_USER=frigate
FRIGATE_CAM01_RTSP_PASS=changeme
|
Warning
The use of env-var substitution for the username
field in the MQTT section of the config requires frigate 0.12 or higher.
At the time of writing (2023.03), the latest stable
release is 0.11
.
Some additional details in this GH issue.
Systemd Unit for Frigate
The /etc/systemd/system/frigate.service
file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| [Unit]
Description=Frigate NVR
# Don't start until after docker is healthy and the nfs share is mounted
After=docker.service
Requires=docker.service
Requires=mnt-frigate.mount
[Service]
User=frigate
Group=frigate
TimeoutStartSec=0
Restart=always
# We want to start with a fresh container every time
ExecStartPre=-/usr/bin/docker exec %n stop
ExecStartPre=-/usr/bin/docker rm %n
ExecStartPre=/usr/bin/docker pull blakeblackshear/frigate:stable
# Expose the web UI on port 80 to keep things a bit cleaner
ExecStart=/usr/bin/docker run --rm --name %n \
--env-file /etc/frigate/secrets \
--mount type=tmpfs,target=/tmp/cache,tmpfs-size=1000000000 \
--device /dev/dri/renderD128 \
--device /dev/apex_0:/dev/apex_0 \
--device /dev/apex_1:/dev/apex_1 \
--shm-size=64m \
-v /mnt/frigate/storage:/media/frigate \
-v /mnt/frigate/config:/config:ro \
-v /etc/localtime:/etc/localtime:ro \
-p 80:5000 \
-p 1935:1935 \
blakeblackshear/frigate:stable
[Install]
WantedBy=default.target
|
Then enable/start the service
1
2
3
4
5
6
7
8
9
10
| karl@nvr:~$ sudo systemctl enable frigate.service
Created symlink /etc/systemd/system/default.target.wants/frigate.service → /etc/systemd/system/frigate.service.
karl@nvr:~$ sudo systemctl start frigate.service
<...>
karl@nvr:~$ sudo systemctl status frigate
● frigate.service - Frigate NVR
Loaded: loaded (/etc/systemd/system/frigate.service; enabled; preset: enabled)
Active: active (running) since <...>
CGroup: /system.slice/frigate.service
└─27104 /usr/bin/docker run --rm --name frigate.service --mount type=tmpfs,target=/tmp/cache,tmpfs-size=1000000000 --device /dev/dri/renderD128 --device /dev/apex_0:/dev/apex_0 --device /dev/apex_1:/dev/apex_1 --shm-size=>
|
And that’s all there is to it :).
Hopefully that helps!