Blog

Self-Host Immich and Frigate with OpenVINO on a GPU VPS

Run Immich (photo search, face recognition) and Frigate NVR (object detection) on a Linux VPS with an Intel GPU: OpenVINO for AI, VAAPI for video, Docker Compose files, and how to reach your home cameras.

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 GPUOpenVINO on CPU
Inference time7-13 ms5-13 ms
CPU used by the detector3-5 %84-95 % of a core
Whole iGPU usage10-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

UseSuggested plan
Immich for one family, or Frigate with 1-2 camerasStart (2 vCPU, 8 GB, 60 GB)
Immich + Frigate together, or 3-5 camerasPower (4 vCPU, 16 GB, 120 GB)
Large photo library, long recording retentionPro (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).