"docker compose up" could not work, which means the F0 acceptance
criterion was not being met:
- ghcr.io/rspamd/rspamd:3.9 returns "denied" and clamav/clamav:1.3 does
not exist. Both were verified against the registries; the working
references are rspamd/rspamd:3.9 and clamav/clamav:1.4.
- Rspamd received RSPAMD_REDIS_SERVERS and RSPAMD_REDIS_PASSWORD as
environment variables it never reads. Confirmed in the container that
it does not expand ${VAR} in UCL: the password reached Redis as that
literal string. Greylisting and ratelimit keep their state in Redis,
so both were silently doing nothing while appearing configured. The
credential is now rendered at startup into override.d, a tmpfs, so it
touches neither the image nor a volume.
- The ratelimit module ships enabled with no rates defined, which
accepts everything. ratelimit.conf defines the per-IP, per-user and
per-recipient buckets of section 5.2.
Verified by bringing redis and rspamd up: the real password resolves, the
rates load, and a probe message returns "Action: greylist" with the state
written to Redis.
Services also gained an image: alongside build:, so the deploy host pulls
instead of compiling while "docker compose build" still works locally.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
82 lines
3.1 KiB
Bash
82 lines
3.1 KiB
Bash
# --- Container images ---
|
|
# Published to GHCR by .github/workflows/release.yml, multi-architecture
|
|
# (linux/amd64 and linux/arm64). The deploy host pulls these instead of
|
|
# compiling: `docker compose pull && docker compose up -d`. Running
|
|
# `docker compose build` locally still overrides them with a local build.
|
|
# Pin IMAGE_TAG to a released version (e.g. 1.2.3) or a sha- tag to roll back.
|
|
IMAGE_NAMESPACE=ghcr.io/lucasrguerra/lambdamail
|
|
IMAGE_TAG=latest
|
|
|
|
# --- Postgres ---
|
|
POSTGRES_DB=lambdamail
|
|
POSTGRES_USER=lambdamail
|
|
POSTGRES_PASSWORD=change-me-in-local-dev
|
|
|
|
# --- Redis ---
|
|
REDIS_PASSWORD=change-me-in-local-dev
|
|
|
|
# --- Shared secrets (local dev values — never reuse in production) ---
|
|
JWT_SECRET=local-dev-jwt-secret-change-me
|
|
# Encrypts DKIM private keys and provider tokens at rest. Losing it makes every
|
|
# stored secret unrecoverable — back it up separately from the database.
|
|
LAMBDAMAIL_MASTER_KEY=local-dev-master-key-32-bytes-min
|
|
|
|
# --- Host identity ---
|
|
PRIMARY_MAIL_HOST=mail.localhost
|
|
MAIL_DOMAIN=localhost
|
|
PUBLIC_IPV4=127.0.0.1
|
|
PUBLIC_IPV6=
|
|
|
|
# --- DNS automation (Cloudflare) ---
|
|
# The token needs Zone.DNS:Edit plus Zone.Zone:Read on the zone. Leave it empty
|
|
# to disable reconciliation; the service then logs that DNS sync is off.
|
|
CLOUDFLARE_API_TOKEN=
|
|
DNS_SYNC_INTERVAL_SECONDS=21600
|
|
|
|
# --- TLS ---
|
|
# traefik: read the certificate Traefik manages (default, DANE not possible).
|
|
# acme: obtain the certificate ourselves via DNS-01. Required for DANE,
|
|
# because only this mode can reuse a pre-generated key.
|
|
TLS_MODE=traefik
|
|
# Used only when TLS_MODE=acme. Leave ACME_DIRECTORY_URL empty for Let's
|
|
# Encrypt production; point it at the staging directory while testing, since
|
|
# production rate limits can lock a domain out for a week.
|
|
ACME_EMAIL=
|
|
ACME_DIRECTORY_URL=
|
|
COOLIFY_PROXY_DIR=./local/acme
|
|
TRAEFIK_ACME_FILENAME=acme.json
|
|
CERT_POLL_INTERVAL_SECONDS=60
|
|
# Static certificate files, used when no ACME store is available (local dev).
|
|
PROTOCOLS_CERT_PATH=/etc/lambdamail/certs/cert.pem
|
|
PROTOCOLS_KEY_PATH=/etc/lambdamail/certs/key.pem
|
|
PROTOCOLS_SPOOL_DIR=/var/lambdamail/spool
|
|
|
|
# --- Transport security policy ---
|
|
# Start at "testing": senders honour the policy but do not hard-fail on it.
|
|
# Promote to "enforce" only once TLS-RPT reports come back clean.
|
|
MTA_STS_MODE=testing
|
|
OUTBOUND_MTA_STS_ENABLED=true
|
|
# DANE stays off unless TLS_MODE=acme. Under Traefik-managed certificates the
|
|
# key changes at every renewal and the published TLSA record stops matching,
|
|
# which permanently rejects mail at every validating MTA.
|
|
OUTBOUND_DANE_ENABLED=false
|
|
DNSSEC_RESOLVER=1.1.1.1:53
|
|
MAX_MESSAGE_BYTES=52428800
|
|
|
|
# ARC seals accepted messages so a downstream receiver can still tell that the
|
|
# mail authenticated at the first hop. Only worth enabling on a host that
|
|
# forwards mail onward.
|
|
ARC_SEAL_ENABLED=false
|
|
|
|
# --- Smarthost (for hosts where outbound port 25 is blocked) ---
|
|
# Setting RELAY_HOST routes all delivery through the relay and adds it to the
|
|
# published SPF record, without which every relayed message fails SPF.
|
|
RELAY_HOST=
|
|
RELAY_PORT=587
|
|
RELAY_USER=
|
|
RELAY_PASS=
|
|
|
|
# --- Webmail ---
|
|
WEBMAIL_HOST=localhost:3000
|
|
ADMIN_HOST=
|