fix(ntfy): publish ntfy host port so the external reverse proxy can reach it
All checks were successful
PR Checks / bot-install (pull_request) Successful in 18s
PR Checks / client-build (pull_request) Successful in 30s
PR Checks / server-tests (pull_request) Successful in 9m29s

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 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 03:57:32 -05:00
parent 4f1a4902e8
commit 4151f7d44e
3 changed files with 26 additions and 5 deletions

View File

@@ -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

View File

@@ -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> -> 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

View File

@@ -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