Over-engineered play stack for infinidim.net — Stalwart wired into the shared Postgres + Redis + Garage S3 over the tailnet, with no WAN presence. Public mail ports are fronted by a separate caddy-l4 layer-4 proxy (caddy/) that can run on any tailnet host tagged tag:reverse-proxy — decoupled from the mailbox. - docker-compose.yml: ts-stalwart sidecar + stalwart, backends via MagicDNS - config/config.toml: PG (data/fts) + Redis (lookup) + S3 (blob) strawman - caddy/: xcaddy build with caddy-l4, JSON layer-4 mail proxy, own compose - acl-snippet.hujson: tag:stalwart owner + backend/edge grants - .env.example + gitignored .env (pulled from shared infra) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
73 lines
2.6 KiB
YAML
73 lines
2.6 KiB
YAML
# tailwart — Stalwart mailbox as a Tailscale sidecar (NO WAN presence).
|
|
#
|
|
# The container shares ts-stalwart's network namespace, so its only interfaces
|
|
# are lo and tailscale0. All mail ports listen on the tailnet only; the public
|
|
# edge is the separate caddy/ layer-4 proxy, which can run on another host.
|
|
#
|
|
# Prereq: the shared tailnet infra (Postgres the-record-prod, Redis
|
|
# slo-time-prod, Garage) must be up, and the stalwart role/db/bucket created
|
|
# (see README). Bring up: docker compose up -d
|
|
|
|
name: tailwart
|
|
|
|
services:
|
|
|
|
ts-stalwart:
|
|
image: tailscale/tailscale:latest
|
|
hostname: ${STALWART_MAGIC_NAME}
|
|
environment:
|
|
TS_AUTHKEY: ${TS_OAUTH_CLIENT_SECRET}?ephemeral=true
|
|
TS_EXTRA_ARGS: --advertise-tags=tag:stalwart
|
|
TS_HOSTNAME: ${STALWART_MAGIC_NAME}
|
|
TS_ACCEPT_DNS: "true"
|
|
TS_AUTH_ONCE: "true"
|
|
TS_USERSPACE: "false"
|
|
TS_ENABLE_HEALTH_CHECK: "true"
|
|
TS_LOCAL_ADDR_PORT: "127.0.0.1:9002"
|
|
dns: [1.1.1.1, 1.0.0.1]
|
|
devices:
|
|
- /dev/net/tun:/dev/net/tun
|
|
cap_add:
|
|
- NET_ADMIN
|
|
- NET_RAW
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:9002/healthz"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 6
|
|
start_period: 30s
|
|
restart: unless-stopped
|
|
|
|
stalwart:
|
|
image: stalwartlabs/mail-server:latest
|
|
network_mode: "service:ts-stalwart"
|
|
environment:
|
|
# Consumed by config/config.toml via its %{env:NAME}% macros. Keeping
|
|
# secrets in env (not the mounted toml) means the toml is commit-safe.
|
|
STALWART_DB_NAME: ${STALWART_DB_NAME}
|
|
STALWART_DB_USER: ${STALWART_DB_USER}
|
|
STALWART_DB_PASSWORD: ${STALWART_DB_PASSWORD}
|
|
DB_HOST: ${DB_MAGIC_NAME}.${TS_TAILNET}
|
|
REDIS_URL: redis://${REDIS_MAGIC_NAME}.${TS_TAILNET}:6379/${STALWART_REDIS_DB}
|
|
S3_ENDPOINT: http://${GARAGE_MAGIC_NAME}.${TS_TAILNET}:3900
|
|
S3_REGION: ${GARAGE_REGION}
|
|
S3_BUCKET: ${STALWART_S3_BUCKET}
|
|
S3_ACCESS_KEY: ${GARAGE_ACCESS_KEY_ID}
|
|
S3_SECRET_KEY: ${GARAGE_SECRET_ACCESS_KEY}
|
|
STALWART_HOSTNAME: ${STALWART_HOSTNAME}
|
|
STALWART_DOMAIN: ${STALWART_DOMAIN}
|
|
STALWART_SMARTHOST: ${STALWART_SMARTHOST}
|
|
STALWART_FALLBACK_ADMIN_SECRET: ${STALWART_FALLBACK_ADMIN_SECRET}
|
|
volumes:
|
|
- ./config/config.toml:/opt/stalwart-mail/etc/config.toml:ro
|
|
# Local working dir only (logs, ACME cache, queue spool). The bulk data
|
|
# lives in Postgres + Garage, not here — but Stalwart still wants a home.
|
|
- stalwart-data:/opt/stalwart-mail
|
|
depends_on:
|
|
ts-stalwart:
|
|
condition: service_healthy
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
stalwart-data:
|