# How this runs on the host. There is one service and no database: the relay # keeps nothing, which is deliberate — an outbox of everyone's reminders is a # liability, and the providers already have delivery logs. # # The two bind addresses below are the security boundary of this whole service. # Neither is routable from outside the machine: # # 127.0.0.1 — for a human on the host, and for the health check. # 172.17.0.1 — the docker bridge, so sibling containers can reach it by an # address that exists for them. `127.0.0.1` inside a consumer's # container is that container, so a consumer configured with a # loopback gateway URL fails every delivery and looks fine. # # It must never be given a 0.0.0.0 binding or a cloudflared route. An # authenticated relay on the public internet is an open spam relay the moment # the token leaks, and that token is copied into every consumer's environment. services: channel-exit: image: ${CHANNEL_EXIT_IMAGE:-channel-exit:local} build: . restart: unless-stopped env_file: .env ports: - "127.0.0.1:${CHANNEL_EXIT_PORT:?allocate a port in the 5400-5999 range}:8080" - "${DOCKER_BRIDGE_IP:-172.17.0.1}:${CHANNEL_EXIT_PORT:?}:8080" healthcheck: test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8080/healthz', timeout=4).status == 200 else 1)"] interval: 15s timeout: 5s start_period: 10s retries: 3 logging: # Bounded, because this log names who was messaged and when. It should # not accumulate on disk indefinitely. driver: json-file options: max-size: "10m" max-file: "3"