Services that need to reach a person hand the message here instead of each carrying a SendGrid key. Email goes to SendGrid, SMS to Twilio; whatsapp and push answer 501 rather than pretending. It binds loopback and the docker bridge only. 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. The log names a channel, a redacted address and the provider's answer. Never the subject, never the body.
40 lines
1.7 KiB
YAML
40 lines
1.7 KiB
YAML
# 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"
|