-
Notifications
You must be signed in to change notification settings - Fork 17
Deployment
Running TinyIce in production: systemd, Docker, a reverse proxy, TLS, zero- downtime updates, and how big a box you actually need.
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 credentialsThe 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 startor 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 run -d --name tinyice \
-p 8000:8000 -p 1935:1935 -p 9000:9000/udp \
-v tinyice-data:/data \
ghcr.io/datanoisetv/tinyice:latest- Mount
/datasotinyice.json,history.db, playlists, and ACME certs persist. - Publish
1935(RTMP) and/or9000/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.
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.
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:
-
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.) -
Set
trusted_proxies. Add the proxy's address soX-Forwarded-Foris honoured — otherwise every client looks like the proxy and bans/scan-detection break. See Security.
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' ./tinyicePoint 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.
-
Config reload:
./tinyice reload(orkill -HUP <pid>) re-readstinyice.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 sendSIGTERMto the old process for a graceful handoff. Admin → hot-swap triggers the same flow.
Details and signal reference in Command Line and Signals.
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.
ulimit -n 65535 # each listener uses one FD + one goroutineRaise 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
Repository · Releases · Issues · Security policy · Apache-2.0
Getting started
Streaming
Integrations
Operations
Internals
Help