Architecture · built to be forwarded

How LightNVR Cloud is actually built.

One annotated diagram, the components that matter, three labeled data flows, and the failure modes we will not pretend away. Written for the engineer your IT lead is about to forward this URL to.

End-to-end shape

Cameras at your site reach a pod we run for you. Recording lands on a volume only that pod can mount. Browsers watch live over WebRTC and play back over HLS. Everything else on this page is a deeper read on one of the boxes below.

End-to-end architecture: cameras at the customer site connect over an encrypted WireGuard tunnel to a per-tenant Kubernetes pod that contains the WireGuard sidecar, go2rtc, and the LightNVR recorder. Recordings land on a dedicated encrypted PersistentVolumeClaim. Browsers watch live video over WebRTC, with COTURN providing NAT traversal, and play back over HLS.
  • Customer site

    Cameras stay on the local network. A WireGuard peer (router, mini-PC, or Raspberry Pi) is the only thing that talks to us — no port-forwarding, no DDNS, no exposed RTSP.

  • Tenant pod

    One pod per tenant in a dedicated Kubernetes namespace. The pod runs the WireGuard sidecar, go2rtc, and LightNVR. Cross-namespace traffic is denied by default.

  • Storage

    A PersistentVolumeClaim backed by DigitalOcean Block Storage, encrypted at rest by the provider, and mounted only by your pod. Recordings never traverse the provisioning Postgres.

  • Viewer path

    Live view is WebRTC (DTLS-SRTP), with COTURN as the relay when a direct path is not available. Recorded playback is HLS over TLS. The relay forwards encrypted bytes; it does not see media.

Components

What each piece is, what it owns, and what it deliberately does not own. The boundary between components matters more than the components themselves.

  • LightNVR core

    The recorder process. Indexes streams, segments video, serves clips and HLS playback, exposes the dashboard API.

    Owns:
    Recording, retention rotation, the dashboard API, clip exports.
    Does not own:
    WebRTC negotiation (delegated to go2rtc), tunnel termination (delegated to the WG sidecar), identity (delegated to the member portal).

    Same binary the open-source community runs on Pis and NAS boxes — there is no "cloud-only" fork.

  • go2rtc

    RTSP/ONVIF normalizer and WebRTC bridge. Reads camera feeds once and serves them as RTSP, HLS, and WebRTC.

    Owns:
    Format conversion, WebRTC offer/answer + ICE, SRTP key negotiation.
    Does not own:
    Persisting recordings (LightNVR), authentication (the dashboard validates the session).
  • WireGuard sidecar

    A WireGuard endpoint inside the tenant pod. Cameras at the customer site reach it through a peer running on a router, mini-PC, or Raspberry Pi.

    Owns:
    Encrypted transport between site and pod, peer key exchange.
    Does not own:
    Inspecting traffic — it is a pure tunnel terminator.

    Implemented in app/services/k8s/wireguard_sidecar.py.

  • COTURN relay

    TURN server for WebRTC NAT traversal. Used only when a viewer's network blocks a direct WebRTC path to the tenant pod.

    Owns:
    Relaying opaque DTLS-SRTP packets.
    Does not own:
    Decrypting media. The relay forwards encrypted bytes; keys never reach it.

    Runs in the shared lightnvr namespace at LoadBalancer 138.197.226.46.

  • Kubernetes

    One namespace per tenant (e.g., nvr-yourcompany). A NetworkPolicy named deny-cross-namespace blocks tenant-to-tenant traffic by default.

    Owns:
    Pod scheduling, network isolation between tenants, dedicated CPU/memory quota per pod.
    Does not own:
    Hypervisor-level isolation — by default tenants share underlying hosts.

    Namespace creation lives in app/services/k8s/namespace.py; pod spec in app/services/k8s/provisioner.py.

  • DigitalOcean Block Storage

    A dedicated PersistentVolumeClaim per tenant. Encrypted at rest by the storage provider; mounted only into that tenant's pod.

    Owns:
    Recording bytes, segment files, the local SQLite indexes inside the volume.
    Does not own:
    Tenant identity, billing state, account metadata — those live in the provisioning Postgres.
  • Provisioning service + Postgres

    The control plane. Handles signup, provisions the tenant's namespace + pod + PVC, and tracks lifecycle state in a managed Postgres.

    Owns:
    Tenant metadata: name, region, allocation, billing identifier, instance state.
    Does not own:
    Recordings. The Postgres has never held a video frame and is not on the recording path.
  • Member portal

    Identity layer. Issues sessions for the dashboard and links a logged-in user to their tenant.

    Owns:
    Authentication, password hashing, session tokens.
    Does not own:
    Authorization decisions inside a tenant — those are enforced in the LightNVR API and at the Kubernetes layer.
  • Demetered

    Usage meter. Records the daily allocation (CPU, RAM, storage) of each tenant pod and produces an arrears invoice.

    Owns:
    Metered usage capture, invoice line-item assembly.
    Does not own:
    Payment capture (Stripe) or sending the invoice email (Mailjet).

    Invoiced in arrears — you pay for what your pod actually held last cycle, not a reserved seat.

Data flow walkthroughs

Three flows cover ninety percent of what we do: writing a recording, serving a live view, and provisioning a brand-new tenant.

a. Camera → cloud recording (write path)

A camera publishes RTSP to your local WireGuard peer. The peer wraps it in the encrypted tunnel, the WG sidecar inside your tenant pod terminates the tunnel, and go2rtc hands the normalized stream to the LightNVR recorder. The recorder writes fragmented MP4 segments to your PVC and rotates them against the retention policy you set.

The write path: camera publishes RTSP through the WireGuard tunnel to the tenant pod's WireGuard sidecar, then to go2rtc, then to the LightNVR recorder, which writes fragmented MP4 segments to the encrypted PersistentVolumeClaim.

b. Browser live view (WebRTC path)

The browser opens the dashboard over TLS and signals a WebRTC session against the tenant pod's go2rtc. ICE picks the best path; if NAT prevents a direct connection, traffic relays through COTURN. Media is DTLS-SRTP end to end — the relay forwards opaque bytes and never holds the keys.

The live-view path: browser signals over HTTPS to the tenant pod's go2rtc, then opens a DTLS-SRTP WebRTC session. When the direct path is blocked by NAT, the COTURN relay forwards encrypted SRTP bytes between the two endpoints without decrypting them.

c. First-time tenant provisioning

Signup hits the member portal for identity. The provisioning service then creates the tenant's namespace, applies the deny-cross-namespace NetworkPolicy, binds the PVC, and schedules the pod. Postgres only stores tenant metadata — never recordings — and the dashboard redirects the new tenant straight into their instance.

The provisioning path: a signup is authenticated by the member portal, then the provisioning service creates a Kubernetes namespace, applies the NetworkPolicy, binds the PersistentVolumeClaim, and schedules the tenant pod. Tenant metadata is recorded in the provisioning Postgres; recordings never live there.

Scaling model

One tenant per pod is the unit. We do not stack tenants in a shared recorder process. Growth happens by giving the pod more of what it needs.

Vertical: sliders, not seats

CPU, RAM, and storage are independent sliders on your tenant. More cameras typically means more storage and a touch more CPU; longer retention is a storage move; more concurrent viewers is a CPU move. Demetered captures the actual daily allocation and the next invoice arrives in arrears for what the pod held.

Horizontal: regions

Region is a provisioning-time choice. The primary cluster is NYC1; AMS3, BLR1, SFO3, and TOR1 are available on request. There is no live cross-region replication today — pick the region you want footage to live in and we will provision there.

  • NYC1 · Live
  • AMS3 · On request
  • BLR1 · On request
  • SFO3 · On request
  • TOR1 · On request

MSP · multi-site

For an MSP managing multiple end-customer sites, the unit is the same: one tenant + one pod + one PVC per client site. There is no shared "fleet" pod — each site has its own isolated recorder. Site rollouts and migration mechanics live on the cloud overview page; the multi-site MSP guide covers the operational story.

Failure modes & recovery

What breaks, what we do about it, and what we do not pretend to handle. Cameras and clouds both fail; the honest version helps you plan.

  • Camera-side internet drop

    If the customer's uplink is down, RTSP cannot reach the tenant pod. The recorder records nothing for that window — there is no edge buffer in the cloud product. Cameras that buffer to an SD card on board will bridge a short outage and back-fill once the tunnel returns; cameras without local storage will simply have a gap.

  • Tenant pod restart

    A pod restart (deploy, node drain, OOM event) takes a small number of seconds. The PVC re-mounts at the same path and recording resumes; the ledger of segments survives because it lives on the volume, not in the pod. There is a small coverage gap during the reconcile window.

  • Volume snapshots

    DigitalOcean Block Storage takes its own infrastructure-level snapshots. We do not currently expose a customer-initiated snapshot/restore in the dashboard. If you need a point-in-time restore today, file a support request and we will pull it from the provider snapshot stream.

  • Region outage

    There is no automatic cross-region failover today. A tenant in NYC1 stays in NYC1. Region selection is a provisioning-time decision, not active-active. If continuity across regions is part of your requirement, talk to us before signup so we size the conversation right.

Trust boundary, in one paragraph

The tenant boundary holds at the namespace and the volume: another tenant cannot reach your pod, and another tenant cannot mount your storage. The infrastructure provider, the LightNVR container image supply chain, and a small operator group remain inside the trust boundary — that is the nature of a managed service. If your threat model treats any of those as out-of-bounds, the dedicated host option below is the relevant lever; the long-form discussion lives on /security#isolation.

Need full host isolation?

By default tenants share underlying physical hosts at the hypervisor layer. For customers with stricter requirements — regulated data, compliance contracts, or kernel-level isolation — we can allocate dedicated hosts. You pick the configuration, we provision the host(s), and you are billed for the full host allocation rather than the per-pod meter.

Contact us about dedicated hosts →

For self-hosters

The same recorder runs on your own hardware. Drop the per-tenant Kubernetes layer and you get the LightNVR + go2rtc pair in a single container, with your own WireGuard (or just a flat LAN) on the camera side. Recording lives on whatever volume you mount — an SSD, a NAS share, a Pi-attached disk. The data flow on this page is the upper bound; self-hosting strips it down to two boxes and a disk.

Install paths, hardware targets, and the docker-compose snippet are on the self-hosted page.

Open-source since 2024 175+ GitHub stars Built in Maine

Have a question we didn't answer?

Email support@lightnvr.com — a real human responds. We would rather have a conversation than ship a page that pretends to be exhaustive.