Deploy Frigate NVR stack with Mosquitto, Ntfy, and frigate-notify #190

Merged
eblume merged 20 commits from deploy-frigate-nvr into main 2026-02-14 21:27:45 -08:00
27 changed files with 1067 additions and 0 deletions
Showing only changes of commit d0360c1585 - Show all commits

Deploy Frigate NVR stack with Mosquitto, Ntfy, and frigate-notify

Add four new services for cloud-free camera recording and alerting:
- Mosquitto MQTT broker (shared service in mqtt namespace)
- Ntfy push notifications (tailnet-accessible)
- Frigate NVR with GableCam via HTTP-FLV, ONNX detection, NFS recordings
- frigate-notify bridging detection events to Ntfy

Also adds Prometheus scrape target, Grafana dashboard, and Caddy
reverse proxy entries for nvr.ops.eblu.me and ntfy.ops.eblu.me.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Erich Blume 2026-02-14 12:39:02 -08:00

View file

@ -76,6 +76,12 @@ caddy_services:
- name: cv
host: "cv.{{ caddy_domain }}"
backend: "https://cv.tail8d86e.ts.net"
- name: nvr
host: "nvr.{{ caddy_domain }}"
backend: "https://nvr.tail8d86e.ts.net"
- name: ntfy
host: "ntfy.{{ caddy_domain }}"
backend: "https://ntfy.tail8d86e.ts.net"
eblume marked this conversation as resolved

Why no mqtt.ops.eblu.me? There might be a reason.

Why no mqtt.ops.eblu.me? There might be a reason.
- name: sifaka
host: "nas.{{ caddy_domain }}"
backend: "http://sifaka:5000"

18
argocd/apps/frigate.yaml Normal file
View file

@ -0,0 +1,18 @@
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: frigate
namespace: argocd
spec:
project: default
source:
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/frigate
destination:
server: https://kubernetes.default.svc
namespace: frigate
syncPolicy:
syncOptions:
- CreateNamespace=true

View file

@ -0,0 +1,18 @@
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mosquitto
namespace: argocd
spec:
project: default
source:
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/mosquitto
destination:
server: https://kubernetes.default.svc
namespace: mqtt
syncPolicy:
syncOptions:
- CreateNamespace=true

18
argocd/apps/ntfy.yaml Normal file
View file

@ -0,0 +1,18 @@
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ntfy
namespace: argocd
spec:
project: default
source:
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/ntfy
destination:
server: https://kubernetes.default.svc
namespace: ntfy
syncPolicy:
syncOptions:
- CreateNamespace=true

View file

@ -0,0 +1,32 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: frigate-notify-config
namespace: frigate
data:
config.yml: |
frigate:
server: http://frigate:5000
alerts:
general:
title: "Frigate Alert"
filters:
labels:
allow:
- person
- car
- dog
- cat
min_score: 0.6
notif:
ntfy:
enabled: true
server: http://ntfy.ntfy.svc.cluster.local:80
topic: frigate-alerts
headers: {}
mqtt:
server: mosquitto.mqtt.svc.cluster.local
port: 1883

View file

@ -0,0 +1,58 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: frigate-config
namespace: frigate
data:
config.yml: |
mqtt:
host: mosquitto.mqtt.svc.cluster.local
port: 1883
go2rtc:
streams:
gablecam:
- "ffmpeg:http://{FRIGATE_CAMERA_USER}:{FRIGATE_CAMERA_PASSWORD}@192.168.1.159/flv?port=1935&app=bcs&stream=channel0_main.bcs#video=copy#audio=copy#audio=opus"
eblume marked this conversation as resolved

here and anywhere else that 192.168.1.159 appears in IaC, include a comment that the IP is reserved in UX7's DHCP config

here and anywhere else that 192.168.1.159 appears in IaC, include a comment that the IP is reserved in UX7's DHCP config
gablecam_sub:
- "ffmpeg:http://{FRIGATE_CAMERA_USER}:{FRIGATE_CAMERA_PASSWORD}@192.168.1.159/flv?port=1935&app=bcs&stream=channel0_sub.bcs"
cameras:
gablecam:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/gablecam
input_args: preset-rtsp-restream
roles: [record]
- path: rtsp://127.0.0.1:8554/gablecam_sub
input_args: preset-rtsp-restream
roles: [detect]
detect:
enabled: true
width: 640
height: 480
objects:
track: [person, car, dog, cat]
detectors:
onnx:
type: onnx
record:
enabled: true
retain:
days: 3
mode: all
alerts:
retain:
days: 30
mode: active_objects
detections:
retain:
days: 14
mode: motion
snapshots:
enabled: true
retain:
default: 14

View file

@ -0,0 +1,34 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frigate-notify
namespace: frigate
spec:
replicas: 1
selector:
matchLabels:
app: frigate-notify
template:
metadata:
labels:
app: frigate-notify
spec:
containers:
- name: frigate-notify
image: ghcr.io/0x2142/frigate-notify:v0.3.5
volumeMounts:
- name: config
mountPath: /app/config.yml
subPath: config.yml
resources:
requests:
memory: "32Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
volumes:
- name: config
configMap:
name: frigate-notify-config

View file

@ -0,0 +1,80 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frigate
namespace: frigate
spec:
replicas: 1
selector:
matchLabels:
app: frigate
template:
metadata:
labels:
app: frigate
spec:
containers:
- name: frigate
image: ghcr.io/blakeblackshear/frigate:0.15.1
ports:
- containerPort: 5000
name: http
- containerPort: 8554
name: rtsp
- containerPort: 1984
name: go2rtc
env:
- name: FRIGATE_CAMERA_USER
valueFrom:
secretKeyRef:
name: frigate-camera
key: username
- name: FRIGATE_CAMERA_PASSWORD
valueFrom:
secretKeyRef:
name: frigate-camera
key: password
volumeMounts:
- name: config
mountPath: /config/config.yml
subPath: config.yml
- name: recordings
mountPath: /media/frigate
- name: database
mountPath: /db
- name: shm
mountPath: /dev/shm
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "2Gi"
cpu: "2000m"
livenessProbe:
httpGet:
path: /api/version
port: 5000
initialDelaySeconds: 30
periodSeconds: 30
readinessProbe:
httpGet:
path: /api/version
port: 5000
initialDelaySeconds: 15
periodSeconds: 10
volumes:
- name: config
configMap:
name: frigate-config
- name: recordings
persistentVolumeClaim:
claimName: frigate-recordings
- name: database
persistentVolumeClaim:
claimName: frigate-database
- name: shm
emptyDir:
medium: Memory
sizeLimit: 256Mi

View file

@ -0,0 +1,22 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: frigate-camera
namespace: frigate
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: frigate-camera
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: Reolink Floodlight Camera
property: username
- secretKey: password
remoteRef:
key: Reolink Floodlight Camera
property: password

View file

@ -0,0 +1,26 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: frigate-tailscale
namespace: frigate
annotations:
tailscale.com/proxy-class: "default"
tailscale.com/proxy-group: "ingress"
gethomepage.dev/enabled: "true"
gethomepage.dev/name: "NVR"
gethomepage.dev/group: "Infrastructure"
gethomepage.dev/icon: "frigate.png"
gethomepage.dev/description: "Network video recorder"
gethomepage.dev/href: "https://nvr.ops.eblu.me"
gethomepage.dev/pod-selector: "app=frigate"
spec:
ingressClassName: tailscale
defaultBackend:
service:
name: frigate
port:
number: 5000
tls:
- hosts:
- nvr

View file

@ -0,0 +1,15 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: frigate
resources:
- external-secret.yaml
- configmap.yaml
- configmap-notify.yaml
- pv-nfs.yaml
- pvc-recordings.yaml
- pvc-database.yaml
- deployment.yaml
- deployment-notify.yaml
- service.yaml
- ingress-tailscale.yaml

View file

@ -0,0 +1,22 @@
# NFS PersistentVolume for Frigate recordings
# Requires: NFS share on sifaka at /volume1/frigate with NFS permissions for indri
#
# To create on Synology:
# 1. Control Panel > Shared Folder > Create
# 2. Name: frigate, Location: Volume 1
# 3. Control Panel > File Services > NFS > NFS Rules
# 4. Add rule for "frigate" share: Hostname=indri, Privilege=Read/Write, Squash=No mapping
apiVersion: v1
kind: PersistentVolume
metadata:
name: frigate-recordings-nfs-pv
spec:
capacity:
storage: 2Ti
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
nfs:
server: sifaka
path: /volume1/frigate
eblume marked this conversation as resolved

fyi I have created this share

fyi I have created this share

View file

@ -0,0 +1,13 @@
# PersistentVolumeClaim for Frigate SQLite database
# Uses minikube's default storage class for local provisioning
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: frigate-database
namespace: frigate
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi

View file

@ -0,0 +1,15 @@
# PersistentVolumeClaim for Frigate recordings
# Binds to the NFS PV for sifaka:/volume1/frigate
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: frigate-recordings
namespace: frigate
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
volumeName: frigate-recordings-nfs-pv
resources:
requests:
storage: 2Ti

View file

@ -0,0 +1,19 @@
---
apiVersion: v1
kind: Service
metadata:
name: frigate
namespace: frigate
spec:
selector:
app: frigate
ports:
- name: http
port: 5000
targetPort: 5000
- name: rtsp
port: 8554
targetPort: 8554
- name: go2rtc
port: 1984
targetPort: 1984

View file

@ -0,0 +1,490 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboard-frigate
namespace: monitoring
labels:
grafana_dashboard: "1"
data:
frigate.json: |
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"mappings": [
{
"options": {
"0": { "color": "red", "index": 0, "text": "DOWN" }
},
"type": "value"
},
{
"options": {
"1": { "color": "green", "index": 1, "text": "UP" }
},
"type": "value"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "red", "value": null },
{ "color": "green", "value": 1 }
]
},
"unit": "short"
},
"overrides": []
},
"gridPos": { "h": 4, "w": 4, "x": 0, "y": 0 },
"id": 1,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"expr": "frigate_service_info",
"refId": "A"
}
],
"title": "Frigate Status",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 15 },
{ "color": "red", "value": 30 }
]
},
"unit": "ms"
},
"overrides": []
},
"gridPos": { "h": 4, "w": 4, "x": 4, "y": 0 },
"id": 2,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"expr": "frigate_detector_inference_speed_seconds{name=\"onnx\"} * 1000",
"refId": "A"
}
],
"title": "Detector Inference",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 5 },
{ "color": "red", "value": 10 }
]
},
"unit": "fps"
},
"overrides": []
},
"gridPos": { "h": 4, "w": 4, "x": 8, "y": 0 },
"id": 3,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"expr": "frigate_camera_fps{camera_name=\"gablecam\"}",
"legendFormat": "{{type}}",
"refId": "A"
}
],
"title": "Camera FPS",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [{ "color": "green", "value": null }]
},
"unit": "short"
},
"overrides": []
},
"gridPos": { "h": 4, "w": 4, "x": 12, "y": 0 },
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"expr": "frigate_detection_total_fps",
"refId": "A"
}
],
"title": "Detection FPS",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": { "type": "linear" },
"showPoints": "never",
"spanNulls": false,
"stacking": { "group": "A", "mode": "none" },
"thresholdsStyle": { "mode": "off" }
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [{ "color": "green", "value": null }]
},
"unit": "percent"
},
"overrides": []
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 },
"id": 5,
"options": {
"legend": {
"calcs": ["mean", "max"],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": { "mode": "multi", "sort": "desc" }
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"expr": "frigate_cpu_usage_percent",
"legendFormat": "{{type}} - {{pid}}",
"refId": "A"
}
],
"title": "CPU Usage",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": { "type": "linear" },
"showPoints": "never",
"spanNulls": false,
"stacking": { "group": "A", "mode": "none" },
"thresholdsStyle": { "mode": "off" }
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [{ "color": "green", "value": null }]
},
"unit": "percent"
},
"overrides": []
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 4 },
"id": 6,
"options": {
"legend": {
"calcs": ["mean", "max"],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": { "mode": "multi", "sort": "desc" }
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"expr": "frigate_mem_usage_percent",
"legendFormat": "{{type}} - {{pid}}",
"refId": "A"
}
],
"title": "Memory Usage",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": { "type": "linear" },
"showPoints": "never",
"spanNulls": false,
"stacking": { "group": "A", "mode": "none" },
"thresholdsStyle": { "mode": "off" }
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [{ "color": "green", "value": null }]
},
"unit": "fps"
},
"overrides": []
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 12 },
"id": 7,
"options": {
"legend": {
"calcs": ["mean", "max"],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": { "mode": "multi", "sort": "desc" }
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"expr": "frigate_camera_fps{camera_name=\"gablecam\"}",
"legendFormat": "{{type}}",
"refId": "A"
}
],
"title": "Camera FPS Over Time",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": { "type": "linear" },
"showPoints": "never",
"spanNulls": false,
"stacking": { "group": "A", "mode": "none" },
"thresholdsStyle": { "mode": "off" }
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [{ "color": "green", "value": null }]
},
"unit": "bytes"
},
"overrides": []
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 12 },
"id": 8,
"options": {
"legend": {
"calcs": ["lastNotNull"],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": { "mode": "multi", "sort": "desc" }
},
"pluginVersion": "10.0.0",
"targets": [
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"expr": "frigate_storage_used_bytes",
"legendFormat": "{{storage}}",
"refId": "A"
}
],
"title": "Storage Usage",
"type": "timeseries"
}
],
"refresh": "30s",
"schemaVersion": 38,
"tags": ["frigate", "nvr", "camera"],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Frigate NVR",
"uid": "frigate",
"version": 1,
"weekStart": ""
}

View file

@ -17,6 +17,7 @@ resources:
- dashboards/configmap-postgresql.yaml
- dashboards/configmap-services.yaml
- dashboards/configmap-zot.yaml
- dashboards/configmap-frigate.yaml
- dashboards/configmap-cv-apm.yaml
- dashboards/configmap-docs-apm.yaml
- dashboards/configmap-flyio.yaml

View file

@ -0,0 +1,10 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mosquitto-config
namespace: mqtt
data:
mosquitto.conf: |
listener 1883
allow_anonymous true
persistence false

View file

@ -0,0 +1,47 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mosquitto
namespace: mqtt
spec:
replicas: 1
selector:
matchLabels:
app: mosquitto
template:
metadata:
labels:
app: mosquitto
spec:
containers:
- name: mosquitto
image: eclipse-mosquitto:2
ports:
- containerPort: 1883
name: mqtt
volumeMounts:
- name: config
mountPath: /mosquitto/config/mosquitto.conf
subPath: mosquitto.conf
resources:
requests:
memory: "32Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
livenessProbe:
tcpSocket:
port: 1883
initialDelaySeconds: 5
periodSeconds: 30
readinessProbe:
tcpSocket:
port: 1883
initialDelaySeconds: 3
periodSeconds: 10
volumes:
- name: config
configMap:
name: mosquitto-config

View file

@ -0,0 +1,8 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: mqtt
resources:
- configmap.yaml
- deployment.yaml
- service.yaml

View file

@ -0,0 +1,13 @@
---
apiVersion: v1
kind: Service
metadata:
name: mosquitto
namespace: mqtt
spec:
selector:
app: mosquitto
ports:
- name: mqtt
port: 1883
targetPort: 1883

View file

@ -0,0 +1,48 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ntfy
namespace: ntfy
spec:
replicas: 1
selector:
matchLabels:
app: ntfy
template:
metadata:
labels:
app: ntfy
spec:
containers:
- name: ntfy
image: binwiederhier/ntfy:v2.11.0
args: ["serve"]
ports:
- containerPort: 80
name: http
volumeMounts:
- name: cache
mountPath: /var/cache/ntfy
resources:
requests:
memory: "32Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /v1/health
port: 80
initialDelaySeconds: 5
periodSeconds: 30
readinessProbe:
httpGet:
path: /v1/health
port: 80
initialDelaySeconds: 3
periodSeconds: 10
volumes:
- name: cache
emptyDir: {}

View file

@ -0,0 +1,26 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ntfy-tailscale
namespace: ntfy
annotations:
tailscale.com/proxy-class: "default"
tailscale.com/proxy-group: "ingress"
gethomepage.dev/enabled: "true"
gethomepage.dev/name: "Ntfy"
gethomepage.dev/group: "Infrastructure"
eblume marked this conversation as resolved

In this same PR, let's move ArgoCD, Prometheus, and devpi/pypi to Infastructure group. CV, Docs, TeslaMate, and Transmission should become "Services".

In this same PR, let's move ArgoCD, Prometheus, and devpi/pypi to Infastructure group. CV, Docs, TeslaMate, and Transmission should become "Services".
gethomepage.dev/icon: "ntfy.png"
gethomepage.dev/description: "Push notifications"
gethomepage.dev/href: "https://ntfy.ops.eblu.me"
gethomepage.dev/pod-selector: "app=ntfy"
spec:
ingressClassName: tailscale
defaultBackend:
service:
name: ntfy
port:
number: 80
tls:
- hosts:
- ntfy

View file

@ -0,0 +1,8 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ntfy
resources:
- deployment.yaml
- service.yaml
- ingress-tailscale.yaml

View file

@ -0,0 +1,13 @@
---
apiVersion: v1
kind: Service
metadata:
name: ntfy
namespace: ntfy
spec:
selector:
app: ntfy
ports:
- name: http
port: 80
targetPort: 80

View file

@ -44,3 +44,9 @@ data:
- job_name: "kube-state-metrics"
static_configs:
- targets: ["kube-state-metrics.monitoring.svc.cluster.local:8080"]
# Frigate NVR metrics
- job_name: "frigate"
static_configs:
- targets: ["frigate.frigate.svc.cluster.local:5000"]
metrics_path: /api/metrics

View file

@ -0,0 +1 @@
Deploy Frigate NVR stack: Frigate for camera recording/detection, Mosquitto MQTT broker, Ntfy for push notifications, and frigate-notify for detection alerting. GableCam connected via HTTP-FLV with ONNX CPU detection, NFS recordings on sifaka, and Grafana dashboard.