Architecture
norsk-ctl is a single binary. When it runs it puts three kinds of process on the host: the daemon itself, an nginx reverse proxy (always — both network modes front every instance through it), and the instance containers — the services each product template defines, plus optional sidecars — for every instance you launch. For the Studio product a template’s services are a Studio + Media pair; that’s the worked example this page uses throughout.
Each instance comes from the product → product template → instance pipeline: launching a product template creates a docker-compose project from the product template’s services, with the runtime concerns layered on by the runner. This page is the runtime topology that pipeline produces.
Process model
Section titled “Process model”- Daemon — the
norsk-ctlbinary running long-lived. Hosts the HTTP API (:8333, local-only), the web UI, the MCP server (at/mcp), and orchestrates Docker for everything else. CLI, web UI, and MCP host all drive this one daemon. - Proxy container —
norsk-proxy, nginx on thenorsk-netDocker bridge. Terminates TLS, enforces authentication, routes/api/…to the daemon on the host,/instance/<id>/…to the right Studio container, and/products/<name>/…to a registered product’s config UI. Always present — both network modes front Studio with it. - Studio + Media per instance — each launched instance is a docker-compose project named from its
INSTANCE_NAME, with astudioservice (web canvas + gRPC client) and amediaservice (the media engine). Compose names the containers<id>-studio-1and<id>-media-1. Both sit onnorsk-netindockermode; the media container moves to the host network inhybridfor low-latency ingest. - Sidecars (optional) — extra services you merge into an instance’s compose project. Named
<id>-<service>-1by compose. See Sidecars.
See Network Modes for how these pieces vary between docker and hybrid networking.
Network topology (Docker mode)
Section titled “Network topology (Docker mode)”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 <id>-media-1 to the host.
Request flow
Section titled “Request flow”- Browser → nginx — nginx publishes a single host port (default 443 across all network modes; see Ports). It’s the only external port the proxy stack listens on (plus port 80 for an HTTP→HTTPS redirect, unless
--no-http-redirectwas passed). - nginx → backend — requests to
/api/,/mcp, and the web UI root are forwarded tohost.docker.internal:8333. The MCP loopback bypass means a localnorsk-ctl mcpstdio bridge talks directly to:8333without going through nginx. - nginx → studio — requests to
/instance/<id>/…are forwarded to<id>-studio-1:8000onnorsk-net, matched by instance ID in the URL path. WebSocket upgrades pass through. - Studio → media — Studio talks to its paired Media container over gRPC (
:6790) and HTTP (:8080) onnorsk-net. - Ingest → media — SRT, RTMP and other ingest ports are published directly from
<id>-media-1to the host. They never touch nginx.
Port uniqueness
Section titled “Port uniqueness”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 to the host, so two instances binding the same host port will clash. Ingest ports must be globally unique across all running instances.
Per-instance composition
Section titled “Per-instance composition”Each instance launches as a docker-compose project. The product template supplies a deliberately minimal compose.yml (just the studio + media images); the runner layers the runtime concerns on at launch, so the same product template runs correctly under different hosts and network modes. The composed file set is:
product template compose.yml (the instance's services)runner override (healthchecks, working-dir volume, studio command + env, license secret)network-mode overlay (picks docker/hybrid)sidecar files (optional — one per --sidecars path)The override and overlays merge over the product template compose, so they fill in (and can override) service definitions. See Products & Templates for what a product template carries.
State and configuration
Section titled “State and configuration”~/.norsk-ctl/config.yaml— daemon settings. Read once at boot, written bynorsk-ctl initandconfig set. See Configuration.~/.norsk-ctl/norsk-ctl.db— SQLite, daemon-managed. Holds instance records, stored product templates, and launch configs (soinstance restartand workflow switches preserve sidecars and other launch options). Migrated forward-only by the daemon at startup.~/.norsk-ctl/products.yaml— registered products (the manifest + registration spec for each). Separate fromconfig.yaml. See Products & Templates.<workingDirectory>/— host-side directory, owned by you. Holdsplugins/,studio-save-files/(workflows),dashboards/. Bind-mounted into Studio.
Port reference
Section titled “Port reference”| Port | Container | Direction | Proxied? | Protocol | Unique across instances? | Notes |
|---|---|---|---|---|---|---|
| 443 (default) | norsk-proxy | host → container :9090 | — | HTTPS | N/A (single proxy) | Proxy listen port; override via proxyPort |
| 80 | norsk-proxy | host → container | — | HTTP | N/A | HTTP→HTTPS redirect + certbot HTTP-01; off with --no-http-redirect |
| 8333 | host (daemon) | proxy → host | Yes | HTTP | N/A | API + web UI + /mcp. Loopback-only; do not expose |
| 8000 | <id>-studio-1 | proxy → container | Yes | HTTP/WS | No — per-container | Studio UI, resolved by container name on norsk-net |
| 6790 | <id>-media-1 | studio → media | No (internal) | gRPC | No — per-container | Media engine API |
| 6791 | <id>-media-1 | container-internal | No (internal) | HTTP | No — per-container | Health check |
| 8080 | <id>-media-1 | studio → media | No (internal) | HTTP | No — per-container | Media HTTP API |
| SRT/RTMP/… | <id>-media-1 | host → container | No (direct) | UDP/TCP | Yes — must be unique | Ingest ports bound directly to host |
TLS is mandatory — the insecure HTTP mode was removed. nginx terminates TLS on proxyPort with:
- TLSv1.3 only, post-quantum cipher group
X25519MLKEM768+TLS_AES_256_GCM_SHA384 - HTTP/2 enabled
Cert sources: mkcert (local dev), self-signed (auto-generated, IP-friendly), user (paths you supply), certbot (Let’s Encrypt — requires a public DNS name). See Security.
Upstream connections (proxy → daemon, proxy → Studio) stay plain HTTP over norsk-net.
See also
Section titled “See also”- Products & Templates — the product → product template → instance model behind every launch
- Network Modes —
dockervshybridtrade-offs - Reverse Proxy — what the proxy gives you and how to manage it
- Security — TLS, auth, cert sources
- Backup & Upgrade — what to preserve across binary upgrades