Skip to content

Architecture

norsk-ctl is a single Go-free binary. When it runs it puts three kinds of process on the host: the daemon itself, an nginx reverse proxy (in Docker mode), and a pair of Studio + Media containers for every instance you launch.

  • Daemon — the norsk-ctl binary running as a long-lived process. Hosts the HTTP API (port 8333), the web UI (served from the same port), and orchestrates Docker for everything else. Users drive the CLI against this daemon.
  • Proxy containernorsk-proxy, an nginx image on the norsk-net Docker bridge. Terminates TLS (if configured), routes /api/… to the daemon on the host and /instances/<id>/… to the right Studio container. Not started in host mode.
  • Studio + Media per instancenorsk-studio-<id> (web canvas + gRPC client) and norsk-media-<id> (the actual media engine). Two containers per instance, both on norsk-net in Docker mode.

See Network Modes for how these pieces vary between docker, hybrid and host networking.

Two traffic classes, and the distinction matters. Proxied traffic (browser requests and API calls) flows through nginx on the Docker bridge. Direct traffic (SRT/RTMP media ingest) bypasses nginx and binds straight from norsk-media to the host.

  1. Browser → nginx — nginx publishes a single configurable port on the host (80, 443, or whatever you set at init). Internally it listens on :9090.
  2. nginx → backend — requests to /api/ are forwarded to host.docker.internal:8333, where the daemon runs on the host.
  3. nginx → studio — requests to /instances/<id>/… are forwarded to norsk-studio-<id>:8000 on norsk-net, matched by instance ID in the URL path. WebSocket upgrades are passed through.
  4. Studio → media — Studio talks to its paired Media container over gRPC (:6790) and HTTP (:8080) on norsk-net.
  5. Ingest → media — SRT, RTMP and other ingest ports are published directly from norsk-media to the host. They never touch nginx.

Internal container ports (8000, 6790, 6791, 8080) are resolved by container name on norsk-net. Multiple instances can all listen on the same internal ports without conflict — Docker routes by name, not by port.

Ingest ports are different. They are published directly from norsk-media-<id> to the host, so two instances binding the same host port will clash. Ingest ports must be globally unique across all running instances.

PortContainerDirectionProxied?ProtocolUnique across instances?Notes
configurable (e.g. 80)norsk-proxyhost → container :9090HTTPN/A (single proxy)Proxy listen port, maps to nginx internal :9090
8333host (daemon)proxy → hostYesHTTPN/A (single daemon)API + web UI, reached via host.docker.internal
8000norsk-studio-<id>proxy → containerYesHTTP/WSNo — per-containerStudio UI, resolved by container name on norsk-net
6790norsk-media-<id>studio → mediaNo (internal)gRPCNo — per-containerMedia engine API
6791norsk-media-<id>container-internalNo (internal)HTTPNo — per-containerHealth check
8080norsk-media-<id>studio → mediaNo (internal)HTTPNo — per-containerMedia HTTP API
SRT/RTMP/…norsk-media-<id>host → containerNo (direct)UDP/TCPYes — must be uniqueIngest ports bound directly to host

When TLS is enabled, nginx terminates it on the proxy port (typically 443). The proxy overlay adds:

  • ssl_certificate / ssl_certificate_key volume mounts
  • TLSv1.3 only, with post-quantum cipher group X25519MLKEM768 and TLS_AES_256_GCM_SHA384
  • HTTP/2 enabled

Upstream connections (proxy → daemon, proxy → Studio) stay plain HTTP over norsk-net.