Self-host Immich and Frigate on a GPU VPS
Immich (a self-hosted Google Photos) and Frigate (a self-hosted NVR for IP cameras) both run AI models all day long: face recognition and smart search for Immich, object detection for Frigate. On the CPU alone, that keeps a small server busy. Both projects support Intel GPUs through OpenVINO, and both use VAAPI / Quick Sync for video.
A Linux GPU VPS gives you exactly that: an Intel UHD 770 GPU inside your VM
(/dev/dri/renderD128), root SSH and Docker already installed. You install
and run everything yourself.
Check that the GPU is there
ls -l /dev/dri/ # renderD128 must exist
vainfo | head -20 # VAAPI profiles (H.264, HEVC, AV1 decode…)
clinfo -l # OpenCL device, used by OpenVINO
If renderD128 is listed, Docker containers can use the GPU with
--device /dev/dri/renderD128.
Immich with OpenVINO
Immich splits into a server and a machine-learning container. The ML
container has an -openvino image that runs CLIP (smart search) and face
recognition on the Intel GPU.
Shortcut: our immich/setup.sh
downloads the official files of the latest release and switches on the lines
below for you. By hand: start from the official docker-compose.yml and
.env of the Immich release you install, then download the two
hardware-acceleration files next to it:
mkdir -p /srv/immich && cd /srv/immich
wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
wget https://github.com/immich-app/immich/releases/latest/download/hwaccel.ml.yml
wget https://github.com/immich-app/immich/releases/latest/download/hwaccel.transcoding.yml
Then edit two services in docker-compose.yml (in the official file, these
extends blocks are already there, commented out with service: cpu):
services:
immich-server:
# … keep the official settings …
extends:
file: hwaccel.transcoding.yml
service: quicksync # or: vaapi
immich-machine-learning:
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}-openvino
extends:
file: hwaccel.ml.yml
service: openvino
# … keep the official volumes / env_file …
docker compose up -d
Last step, in the web interface: Administration → Settings → Video Transcoding → Hardware Acceleration → Acceleration API: Quick Sync, with hardware decoding on. Face detection, smart search and OCR then run on the GPU, and videos are transcoded with Quick Sync.
To check: docker logs immich_machine_learning must say
Setting execution providers to ['OpenVINOExecutionProvider', …] for each
model, and the server log Transcoding video … with QSV-accelerated encoding and decoding.
Tested on 2026-11-04 (Immich v3.3.5, Linux GPU VPS, 4 vCPU, 8 GB): the
face (buffalo_l), CLIP (ViT-B-32__openai) and OCR (PP-OCRv5_mobile)
models all loaded on OpenVINO with no error, the ML process used the GPU
render engine, faces were detected, smart search found the right photos
(“a monkey” → the baboon picture), and an 80-second video was transcoded with
Quick Sync in about 7 seconds.
Storage is the real limit for Immich. Plans come with 60 GB (Start), 120 GB (Power) or 240 GB SSD (Pro). For a large photo library, keep the originals elsewhere (external library, object storage) and let Immich index them.
Frigate with OpenVINO and VAAPI
Frigate decodes every camera stream and runs object detection on each frame where something moves. On an Intel GPU, VAAPI decodes the video and OpenVINO runs the detector, so the CPU stays mostly free.
docker-compose.yml:
services:
frigate:
image: ghcr.io/blakeblackshear/frigate:stable
container_name: frigate
restart: unless-stopped
shm_size: "256mb"
devices:
- /dev/dri/renderD128:/dev/dri/renderD128
volumes:
- ./config:/config
- ./media:/media/frigate
- type: tmpfs
target: /tmp/cache
tmpfs:
size: 1000000000
ports:
- "8971:8971" # web UI (HTTPS, with login)
- "8554:8554" # RTSP restream
config/config.yml, with the OpenVINO model shipped in the Frigate image:
mqtt:
enabled: false
ffmpeg:
hwaccel_args: preset-vaapi
detectors:
ov:
type: openvino
device: GPU
model:
width: 300
height: 300
input_tensor: nhwc
input_pixel_format: bgr
path: /openvino-model/ssdlite_mobilenet_v2.xml
labelmap_path: /openvino-model/coco_91cl_bkgr.txt
cameras:
front_door:
ffmpeg:
inputs:
- path: rtsp://user:password@10.8.0.10:554/stream2 # sub-stream
roles: [detect]
- path: rtsp://user:password@10.8.0.10:554/stream1 # main stream
roles: [record]
detect:
enabled: true # required: detection is off by default in Frigate 0.18
width: 640
height: 360
fps: 5
The files are also in
LinuxGPU-VPS/examples/frigate,
with a test setup that plays a video as a fake RTSP camera.
docker compose up -d, then get the first admin password with
docker logs frigate 2>&1 | grep -i password and open https://<server>:8971.
System metrics shows the OpenVINO inference time and the GPU usage.
Tested on 2026-11-04 (Frigate 0.18.2, Linux GPU VPS, 4 vCPU, 8 GB), one 768×576 camera at 5 fps with pedestrians, 15-25 detections per second:
| OpenVINO on GPU | OpenVINO on CPU | |
|---|---|---|
| Inference time | 7-13 ms | 5-13 ms |
| CPU used by the detector | 3-5 % | 84-95 % of a core |
| Whole iGPU usage | 10-14 % | — |
Video decoding on the GPU kept ffmpeg at about 1 % CPU, and people were detected at 78-96 % confidence. On one camera the CPU keeps up, but it burns a full core; the GPU leaves the CPU free for more cameras and recording.
Reaching cameras that are at home
Your cameras are on your home network, the VPS is in a data center. Frigate needs to pull their RTSP streams, so you need a private tunnel:
- Tailscale (simplest): install it on the VPS and on a small device at home (router, Raspberry Pi), enable subnet routing for the camera network. It works even if your home connection has no public IPv4.
- WireGuard: run the server on the VPS. Every Linux GPU VPS has a dedicated public IPv6, so your router can connect over IPv6; if your home has no IPv6, take the dedicated IPv4 option or use Tailscale.
Plan your upload bandwidth: each camera you record sends its main stream continuously (often 2 to 4 Mbit/s for 1080p). Use the camera’s low-resolution sub-stream for detection, as in the config above, and record the main stream only for the cameras that need it.
Never expose RTSP ports to the internet. Only the tunnel should reach them.
Home Assistant
Keep Home Assistant at home (it talks to Zigbee, Z-Wave and local devices) and connect it to Frigate on the VPS through the same tunnel with the Frigate integration. Events, snapshots and clips then show up in Home Assistant, while the heavy video and AI work runs on the VPS GPU.
Which plan
| Use | Suggested plan |
|---|---|
| Immich for one family, or Frigate with 1-2 cameras | Start (2 vCPU, 8 GB, 60 GB) |
| Immich + Frigate together, or 3-5 cameras | Power (4 vCPU, 16 GB, 120 GB) |
| Large photo library, long recording retention | Pro (6 vCPU, 24 GB, 240 GB SSD) |
The GPU is the same Intel UHD 770 as in our shared GPU load test and our Jellyfin transcoding benchmark. Compose files, configs and check scripts: github.com/LinuxGPU-VPS/examples (immich, frigate).