app: id: podsteadr-mediamtx name: podsteadr MediaMTX version: "1.20.0" description: MediaMTX ingest/output backend for podsteadr — RTMP + WebRTC/WHIP ingest, HLS playback, stream recording. category: media # Hyphenated name matches the podsteadr repo's docker-compose container_name # (podsteadr-mediamtx); alias `mediamtx` is the short hostname podsteadr's # server reaches it by (MEDIAMTX_API_URL=http://mediamtx:9997) and the one # baked into mediamtx.yml's authHTTPAddress callback below. container_name: podsteadr-mediamtx container: image: docker.io/bluenviron/mediamtx:1.20.0 pull_policy: if-not-present network: podsteadr-net network_aliases: [mediamtx] derived_env: # Browsers need a reachable ICE host candidate for WebRTC/WHIP; without # this, the offer only advertises container-internal addresses and # publish/playback negotiation fails for anyone off-host. - key: MTX_WEBRTCADDITIONALHOSTS template: "{{HOST_MDNS}}" dependencies: - storage: 10Gi resources: cpu_limit: 1 memory_limit: 512Mi disk_limit: 10Gi security: # Stock mediamtx image runs as container-root (no USER directive) but # only ever writes to the bind-mounted /recordings — CHOWN/DAC_OVERRIDE # cover the fresh-bind-dir-ownership gap the same way apps/botfights and # apps/immich document (root uid inside the container does not # automatically bypass DAC checks once cap-drop ALL applies). Unverified # against a real install; check first-boot logs on initial deploy. capabilities: [CHOWN, DAC_OVERRIDE] readonly_root: true no_new_privileges: true network_policy: isolated ports: - host: 1935 container: 1935 protocol: tcp auth: none auth_rationale: >- RTMP ingest (OBS). Not HTTP, so the node's session gate has no login page to serve here; publish auth is delegated to podsteadr's own HTTP auth webhook (authHTTPAddress below), which checks a per-stream secret key never exposed in this port mapping. - host: 8889 container: 8889 protocol: tcp auth: none auth_rationale: >- WebRTC/WHIP ingest — browsers publish directly with a per-stream bearer secret checked by podsteadr's auth webhook, the same protocol-level auth as the RTMP port above. - host: 8189 container: 8189 protocol: udp auth: none auth_rationale: >- WebRTC ICE/UDP media transport. Raw UDP has no HTTP session concept for the gate to enforce. - host: 8890 container: 8888 protocol: tcp auth: none auth_rationale: >- Public HLS playback URL, handed out to viewers and podcast/livestream clients outside the node (zap.stream, third-party players). A login page here would break every external viewer; playback is read-only. volumes: - type: bind # Shared with apps/podsteadr (mounted read-only there) so the app can # list and remux finished recordings for one-click episode publishing. source: /var/lib/archipelago/podsteadr/recordings target: /recordings options: [rw] - type: bind source: /var/lib/archipelago/podsteadr-mediamtx/config/mediamtx.yml target: /mediamtx.yml options: [ro] environment: [] files: - path: /var/lib/archipelago/podsteadr-mediamtx/config/mediamtx.yml overwrite: true content: | # MediaMTX configuration for podsteadr. # Ingest: RTMP (OBS) + WebRTC/WHIP (browser). Output: HLS. Publish auth is # delegated to podsteadr via HTTP; stream status is polled from the API. logLevel: info api: yes apiAddress: :9997 # ---- authentication ------------------------------------------------------ authMethod: http authHTTPAddress: http://podsteadr-app:8095/api/mediamtx/auth authHTTPExclude: - action: api - action: metrics - action: pprof # ---- protocols ----------------------------------------------------------- rtsp: no srt: no moq: no rtmp: yes rtmpAddress: :1935 hls: yes hlsAddress: :8888 # Standard HLS, not lowLatency: LL-HLS's small per-part buffering window has very little # tolerance for B-frame reordering (common in most OBS encoder presets), and a real test # stream crashed the muxer twice in ~2 minutes with "too many reordered frames" / "unable to # extract DTS" once frame timing got even slightly irregular. Standard HLS buffers a full # segment before finalizing, which absorbs that jitter — a few extra seconds of latency # instead of intermittent muxer crashes / viewer buffering. hlsVariant: mpegts hlsAlwaysRemux: yes hlsAllowOrigins: ["*"] webrtc: yes webrtcAddress: :8889 webrtcLocalUDPAddress: :8189 webrtcAllowOrigins: ["*"] # ---- recording ----------------------------------------------------------- pathDefaults: record: yes recordPath: /recordings/%path/%Y-%m-%d_%H-%M-%S-%f recordFormat: fmp4 recordPartDuration: 1s recordSegmentDuration: 1h recordDeleteAfter: 168h paths: # Streams live at live/; publish requires the stream secret, # which podsteadr checks in the auth webhook. "~^live/[A-Za-z0-9]+$": {} health_check: # Stock mediamtx image has no shell, so an in-container HTTP probe of the # API isn't meaningfully cheaper than TCP; RTMP liveness is enough (same # polling-not-hooks rationale as podsteadr's own status poller, which # exists precisely because runOn*-style shell hooks aren't available on # this image). type: tcp endpoint: localhost:1935 interval: 30s timeout: 5s retries: 3