From 4151f7d44e5f9460d7cff72afafba2d5e04568d8 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Wed, 22 Jul 2026 03:57:32 -0500 Subject: [PATCH] fix(ntfy): publish ntfy host port so the external reverse proxy can reach it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ntfy service was configured with no published host port, on the assumption that the public reverse proxy shares the compose network and can dial ntfy:80 directly. It does not — Pangolin runs outside the compose network and reaches every service through a published host port (exactly why `app` publishes 3000). With no published port there was nothing for the notification subdomain to forward to, so push delivery could never work in production. Publish container :80 on a host port (NTFY_HOST_PORT, default 2586, binds 0.0.0.0 like `app`) and correct the now-inaccurate comments in docker-compose.yml and ntfy/server.yml. Document NTFY_HOST_PORT in .env.example. No code change — deploy config only. Co-Authored-By: Claude --- .env.example | 6 ++++++ docker-compose.yml | 17 ++++++++++++++--- ntfy/server.yml | 8 ++++++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index 42bbafc..b8f3850 100644 --- a/.env.example +++ b/.env.example @@ -131,6 +131,12 @@ UOLINK_PROTOCOL=1 # register endpoints on a different host than NTFY_BASE_URL. # NTFY_PUBLISH_TOKEN Optional. The content-free-tickle design needs NO token; # set one only to require auth on backend→ntfy publishes. +# NTFY_HOST_PORT Host port the ntfy container publishes :80 on (default +# 2586). The public reverse proxy forwards the notification +# subdomain to host:NTFY_HOST_PORT — required because the +# proxy lives outside the compose network and cannot reach +# ntfy any other way. Change only on a host-port conflict. NTFY_BASE_URL=https://ntfy.example.com # NTFY_ALLOWED_ORIGINS=https://ntfy.example.com # NTFY_PUBLISH_TOKEN= +# NTFY_HOST_PORT=2586 diff --git a/docker-compose.yml b/docker-compose.yml index c7b0a28..a7dcd45 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -74,9 +74,20 @@ services: volumes: - ntfydata:/var/lib/ntfy - ./ntfy/server.yml:/etc/ntfy/server.yml:ro - # No published host port — devices reach ntfy through the public reverse proxy - # on its own hostname; the backend publisher reaches it over the private - # compose network. Never publish this directly. + # Published so the PUBLIC reverse proxy (Pangolin) can forward the + # notification subdomain here. Pangolin lives OUTSIDE the compose network and + # reaches every service through a published host port — never by joining the + # internal network — exactly like `app` above (3000). So ntfy must publish a + # port too: the reverse proxy maps notify. -> host:NTFY_HOST_PORT -> + # ntfy:80. Unlike INTERNAL_PORT / the bot, ntfy is DEVICE-facing, so it is + # SUPPOSED to be reachable through the proxy. Binds 0.0.0.0 (no 127.0.0.1 + # prefix) so Pangolin can reach the container. Both the app (SSE subscribe) and + # the backend (POSTing content-free tickles to each device's registered + # endpoint) reach ntfy on this same public origin — NTFY_ALLOWED_ORIGINS pins + # it — so all ntfy traffic flows through the proxy; there is no separate + # internal publish port. + ports: + - "${NTFY_HOST_PORT:-2586}:80" bot: # Same as app: prebuilt bot image, pulled in production. Build locally via diff --git a/ntfy/server.yml b/ntfy/server.yml index db6c9a0..f6e0488 100644 --- a/ntfy/server.yml +++ b/ntfy/server.yml @@ -13,8 +13,12 @@ # a placeholder for a bare `ntfy serve`. base-url: "https://ntfy.localhost" -# Served on the private compose network; the public reverse proxy terminates TLS -# and forwards to this port. docker-compose.yml publishes NO host port for ntfy. +# ntfy listens on :80 inside the container. docker-compose.yml publishes this on +# a host port (NTFY_HOST_PORT, default 2586) so the public reverse proxy — which +# lives OUTSIDE the compose network — can terminate TLS and forward the +# notification subdomain to it. Both the app (SSE subscribe) and the backend +# (POSTing content-free tickles to registered device endpoints) reach ntfy on +# that public origin, so all traffic flows through the proxy. listen-http: ":80" behind-proxy: true