app: id: podsteadr-blossom name: podsteadr Blossom version: "4" description: Blossom (BUD-02) sha256-addressed media blob server backing podsteadr's episode uploads and covers. category: media # Hyphenated name matches the podsteadr repo's docker-compose container_name # (podsteadr-blossom); alias `blossom` is the short hostname podsteadr's # server reaches it by (BLOSSOM_URL_INTERNAL=http://blossom:3000). container_name: podsteadr-blossom container: image: ghcr.io/hzrd149/blossom-server:4 pull_policy: if-not-present network: podsteadr-net network_aliases: [blossom] # Image runs as container-root (no USER directive) writing to a # bind-mounted /app/data — CHOWN/DAC_OVERRIDE cover the fresh-bind-dir # ownership gap the same way apps/botfights and apps/immich document. # Unverified against a real install; check first-boot logs. data_uid: "0:0" dependencies: - storage: 20Gi resources: cpu_limit: 1 memory_limit: 512Mi disk_limit: 20Gi security: capabilities: [CHOWN, DAC_OVERRIDE, FOWNER] readonly_root: false no_new_privileges: true network_policy: isolated ports: - host: 8098 container: 3000 protocol: tcp auth: none auth_rationale: >- Media blobs (episode audio/video, covers) must be publicly fetchable by podcast clients as RSS enclosure URLs — that's the entire purpose of this port. Uploads are separately gated by blossom's own BUD-02 signed-nostr-event auth (upload.requireAuth below), not a node session; reads are intentionally public per the config's own header comment. volumes: - type: bind source: /var/lib/archipelago/podsteadr-blossom/data target: /app/data options: [rw] - type: bind source: /var/lib/archipelago/podsteadr-blossom/config/config.yml target: /app/config.yml options: [ro] environment: [] files: - path: /var/lib/archipelago/podsteadr-blossom/config/config.yml overwrite: true content: | # blossom-server (v4.x) configuration for podsteadr. # Uploads require a signed nostr auth event (BUD-02, kind 24242); # reads are public so podcast apps can fetch enclosures. # # NOTE (blossom-server 4.4.1 gotcha, do not rediscover): `rules:` MUST # be nested under `storage:` — a top-level `rules:` key is silently # ignored, the ruleset ends up empty, and every upload fails 401 # "Server dose not accept video/mp4 blobs" (typo is theirs). The # GitHub master branch is a Deno rewrite with a different schema # (storage.rules, BUD-11, range support); the `:4` image is the older # node/koa codebase this config targets. publicDomain: "" databasePath: data/sqlite.db dashboard: enabled: false discovery: nostr: enabled: false relays: [] upstream: enabled: false domains: [] storage: backend: local local: dir: ./data/blobs removeWhenNoOwners: false # "expiration" is time since a blob was last accessed — unaccessed # blobs get pruned after this. Podcast media should effectively # never expire, so keep this long. rules: - type: "*" expiration: 10 years upload: enabled: true requireAuth: true requirePubkeyInRule: false list: requireAuth: false allowListOthers: true tor: enabled: false proxy: "" health_check: # No documented health endpoint; TCP liveness on the app port. type: tcp endpoint: localhost:3000 interval: 30s timeout: 5s retries: 3