app: id: podsteadr name: podsteadr version: "1.0.0" description: Self-hosted, nostr-native podcast publishing and livestreaming. Log in with Nostr, upload episodes or go live via OBS/WebRTC, publish to RSS with Podcasting 2.0 lightning payments. category: media # Container/DNS-alias name deliberately NOT "podsteadr" — on a host whose own # hostname happens to be "podsteadr", the host's own /etc/hosts self-hostname # entry (127.0.1.1, e.g. from cloud-init) shadows the container network's DNS # alias for other containers looking up "podsteadr", and mediamtx's auth-webhook # callback resolves to the host's loopback instead of this container — every # RTMP publish gets rejected with "connection refused" (observed on # podsteadr.atobitcoin.io, 2026-07-30; see docker-compose.yml in the podsteadr # repo for the original writeup). Carried forward unchanged into the manifest. container_name: podsteadr-app container: # Built locally from the podsteadr repo (source lives outside this tree — # see apps/podsteadr/README.md + build-from-prototype.sh), same pattern as # apps/indeedhub. Not yet pushed to the shared registry; push-to-registry.sh # is there for when fleet-wide install is needed. image: localhost/podsteadr:1.0.0 pull_policy: if-not-present network: podsteadr-net network_aliases: [podsteadr-app] derived_env: - key: PUBLIC_URL template: "http://{{HOST_MDNS}}:8095" - key: MEDIAMTX_RTMP_PUBLIC template: "rtmp://{{HOST_MDNS}}:1935" - key: MEDIAMTX_WHIP_PUBLIC template: "http://{{HOST_MDNS}}:8889" - key: MEDIAMTX_HLS_PUBLIC template: "http://{{HOST_MDNS}}:8890" - key: BLOSSOM_URL_DEFAULT template: "http://{{HOST_MDNS}}:8098" # node:22-bookworm-slim's built-in `node` user is uid:gid 1000:1000. The # image's own Dockerfile chowns /data to node:node, but that only affects # the image layer — the actual runtime mount is the bind volume below, so # the host directory needs the same ownership or the read-only-root, # non-root `node` process can't open the SQLite DB (unverified against a # real node install; flagging per this repo's convention of documenting # bind-mount ownership assumptions, e.g. apps/botfights/manifest.yml). data_uid: "1000:1000" dependencies: - app_id: podsteadr-mediamtx - app_id: podsteadr-blossom - storage: 2Gi resources: cpu_limit: 2 memory_limit: 1Gi disk_limit: 2Gi security: capabilities: [] readonly_root: true no_new_privileges: true network_policy: isolated ports: - host: 8095 container: 8095 protocol: tcp auth: none auth_rationale: >- podsteadr is a public podcast/livestream server: RSS feeds and the marketplace/catalog API must stay fetchable by third-party podcast clients, crawlers, and other podsteadr instances with no Archipelago session, and the app already gates its own sensitive routes with NIP-98 signed-request auth (see server/src/plugins/nostr-auth.ts in the podsteadr repo). Putting the node's session gate in front would block every external RSS/API consumer without adding real protection. volumes: - type: bind source: /var/lib/archipelago/podsteadr target: /data options: [rw] # Shares podsteadr-mediamtx's recordings directory (rw there, ro here) so # the app can list/remux finished recordings for one-click episode # publishing without granting it write access to live segments. - type: bind source: /var/lib/archipelago/podsteadr/recordings target: /recordings options: [ro] environment: - NODE_ENV=production - PORT=8095 - DATA_DIR=/data - RECORDINGS_DIR=/recordings - MEDIAMTX_API_URL=http://mediamtx:9997 - BLOSSOM_URL_INTERNAL=http://blossom:3000 - NOSTR_RELAYS=wss://relay.damus.io,wss://nos.lol,wss://relay.nostr.band - CASHU_MINT_URL_DEFAULT=https://mint.minibits.cash/Bitcoin # podsteadr's Fastify server (fastify-static) isn't the nginx-served SPA # shape the platform auto-patches for NIP-07 injection (see "Nostr Signer # Bridge" in docs/app-developer-guide.md) — its frontend already # self-references /nostr-provider.js from index.html and vendors a copy at # build time (podsteadr commit 133558d). That vendored copy goes stale # across archy OTA releases, so re-copy the canonical host script over it # on every install/reinstall instead of trusting the baked-in one. hooks: post_install: - copy_from_host: src: "web-ui/nostr-provider.js" dest: /app/public/nostr-provider.js health_check: type: http endpoint: http://localhost:8095 path: /api/health interval: 30s timeout: 5s retries: 3 interfaces: main: name: Web UI description: Podcast dashboard, upload/live wizard, and stream management type: ui port: 8095 protocol: http path: / metadata: author: podsteadr icon: /assets/img/app-icons/podsteadr.png repo: http://146.59.87.168:3000/ssmithx/podsteadr license: MIT tags: - nostr - podcast - livestream - media - rss - lightning launch: open_in_new_tab: false