Skip to content

Deployment

DatanoiseTV edited this page Jun 18, 2026 · 1 revision

Deployment

Running TinyIce in production: systemd, Docker, a reverse proxy, TLS, zero- downtime updates, and how big a box you actually need.

systemd

The .deb/.rpm packages (Installation) install a hardened unit at /lib/systemd/system/tinyice.service and lay down /etc/tinyice (config) and /var/lib/tinyice (state) owned by a dedicated tinyice user.

sudo systemctl unmask tinyice          # installed masked on purpose
sudo systemctl enable --now tinyice
sudo journalctl -u tinyice | grep -A4 "FIRST RUN"   # read generated credentials

The unit runs as the unprivileged tinyice user with NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp, PrivateDevices, MemoryDenyWriteExecute, a restricted address-family/syscall set, and a CapabilityBoundingSet of just CAP_NET_BIND_SERVICE (so it can bind 80/443 without root). ReadWritePaths is limited to /var/lib/tinyice and /etc/tinyice.

It ships masked so a stray systemctl start or distro auto-enable hook can't bring up an unconfigured daemon. Unmask it once, deliberately, after you've reviewed the config.

A reference unit and a FreeBSD rc script also live under contrib/.

Docker

docker run -d --name tinyice \
  -p 8000:8000 -p 1935:1935 -p 9000:9000/udp \
  -v tinyice-data:/data \
  ghcr.io/datanoisetv/tinyice:latest
  • Mount /data so tinyice.json, history.db, playlists, and ACME certs persist.
  • Publish 1935 (RTMP) and/or 9000/udp (SRT) only if you ingest video.
  • Multi-arch images (linux/amd64, linux/arm64); tag matrix in Installation.
  • Update by pulling a new tag and recreating the container — there is no in-process self-updater.

Do I need a reverse proxy?

No. TinyIce is built to face the internet directly:

  • It terminates TLS itself with built-in ACME (Let's Encrypt) — no nginx just to get HTTPS. See Auto-HTTPS below.
  • The shared circular-buffer architecture serves large numbers of concurrent connections from a single process; the ceiling is your uplink bandwidth, not a proxy (sizing).
  • It already does TLS redirect for browsers while letting plain-HTTP hardware encoders keep talking to the HTTP port — the router distinguishes them, so you don't need a proxy to split that traffic.

Running TinyIce straight on ports 80/443 with auto_https is a first-class, recommended deployment.

If you still want one

A reverse proxy is optional — reach for it only for a concrete reason (centralised TLS across many services, WAF/rate-limiting at the edge, path-based routing, offloading TLS CPU at very large fleets). When you do, two things matter:

  1. Don't buffer the stream. Disable proxy response buffering on the stream, HLS, and SSE paths, or listeners get latency and stalls. (nginx: proxy_buffering off; plus SSE-friendly settings for /events.)
  2. Set trusted_proxies. Add the proxy's address so X-Forwarded-For is honoured — otherwise every client looks like the proxy and bans/scan-detection break. See Security.

Auto-HTTPS (ACME)

Let TinyIce manage certificates itself — no proxy required:

{
    "use_https": true, "auto_https": true,
    "port": "80", "https_port": "443",
    "domains": ["radio.example.com"], "acme_email": "admin@example.com"
}

Ports 80 and 443 must be reachable for the ACME challenge. On Linux without root, grant the bind capability (the package does this for you):

sudo setcap 'cap_net_bind_service=+ep' ./tinyice

Point acme_directory_url at a custom CA (Step-CA, ZeroSSL) if you're not using Let's Encrypt. Manual cert/key pairs go in cert_file / key_file.

Zero-downtime operations

  • Config reload: ./tinyice reload (or kill -HUP <pid>) re-reads tinyice.json — mounts, passwords, relays, UI — without dropping listeners.
  • Binary hot-swap: the listening sockets use SO_REUSEADDR + SO_REUSEPORT, so you can start a new build alongside the old one; both share traffic, then send SIGTERM to the old process for a graceful handoff. Admin → hot-swap triggers the same flow.

Details and signal reference in Command Line and Signals.

Sizing

The dominant constraint is almost always network bandwidth, not CPU or memory. With the shared circular-buffer architecture (Architecture), per-listener overhead is small (roughly the TCP connection state plus a signal channel); each mount holds one pre-allocated buffer (~512 KB by default).

Rule of thumb at 128 kbps: ~8 listeners per Mbps of uplink, so ~7,000–8,000 listeners saturate a 1 Gbps line — that's your ceiling before CPU or RAM is.

Hardware Network Approx. listeners @128 kbps
Raspberry Pi 4 (4-core, 4 GB) 1 Gbps shared bus 1,000–1,500
1 vCPU / 2 GB VPS 1–2 Gbps burst 2,000–3,000
4–8 core / 16 GB dedicated 1 Gbps dedicated 5,000–7,000 (saturates the line)
16+ core / 32 GB 10 Gbps 20,000+ (OS tuning dominates)

These are bandwidth-bound estimates; treat them as starting points and measure your own workload. TLS termination and bcrypt-on-connect add CPU; for very large fleets, offload TLS to the proxy/load balancer.

OS tuning for high listener counts

ulimit -n 65535          # each listener uses one FD + one goroutine

Raise file-descriptor limits (systemd: LimitNOFILE=), and on busy boxes tune the TCP stack (somaxconn, ephemeral port range). Enable low-latency mode to drop HTTP-level buffering and shrink per-mount burst buffers when you're memory-constrained.


Next: Observability · Security · Command Line and Signals

Clone this wiki locally