mirror of
https://github.com/akitaonrails/ai-memory.git
synced 2026-10-02 03:24:46 +08:00
Per Phase 2 decision to NOT terminate TLS in-binary: ai-memory
stays HTTP-on-loopback by default (zero change for existing users
on upgrade) and operators front it with a battle-tested reverse
proxy when they cross the deployment shapes that need TLS.
Three new files:
- docs/https-via-proxy.md — the deployment guide. Covers:
- When you DON'T need TLS (loopback + stdio cases honestly
don't, called out up front so we don't add ceremony where
it doesn't earn its keep).
- When you DO need TLS (multi-user, non-loopback bind, /web
from another machine, public exposure).
- Five deployment paths with copy-paste configs:
- Caddy + public domain + Let's Encrypt
- Caddy + internal CA (LAN-only)
- Cloudflare Tunnel (no open ports, TLS at the edge)
- External cert files (Caddy or nginx)
- Native (non-Docker) Caddy
- Each path documents what can go wrong + the trust-install
step for the internal-CA case (the load-bearing manual
step — skipping it produces security theatre).
- Final section "Don't paper over the security gap" calls out
three specific anti-patterns operators reach for when
things "don't work" (disable allowed-hosts, --insecure on
clients, --no-tls-verify on cloudflared).
- docker/compose.tls.caddy.yml — copy-paste compose template
with Caddy front. Three variants documented inline (LE,
internal CA, external cert files). Same service definition
across all three; only the Caddyfile differs.
- docker/compose.tls.cloudflared.yml — Cloudflare Tunnel sidecar
compose template. Walks through the one-time CF dashboard
setup, the .env.production shape (incl. CLOUDFLARE_TUNNEL_TOKEN),
and the client-config commands. No ports exposed; the tunnel is
outbound-only.
Code change (small):
- crates/ai-memory-cli/src/commands/serve.rs: extend the
existing non-loopback startup warning. Previously fired only
when bind wasn't loopback AND no auth token was configured;
now ALSO fires (with different message) when bind isn't
loopback AND auth IS configured — to remind the operator
that bearer tokens still travel cleartext on plain HTTP and
point them at docs/https-via-proxy.md. One-shot at startup,
not a refusal to serve (operators may already be behind a
proxy we can't reliably detect; refusing to bind would break
their flow). Single line of log output, links to the doc.
Cross-linking:
- README "Security" section gets a clear "Want HTTPS?"
paragraph pointing at docs/https-via-proxy.md + the two
compose templates. Spells out the loopback exception so the
single-user happy-path user doesn't think they're missing
something.
- README docs table updated: docs/deploy.md row reframed as
"pointers to the TLS guide"; new row added for
docs/https-via-proxy.md with a full description of what it
covers.
- docs/deploy.md's brief "encrypted transport" section
trimmed from inline two-paragraph table to pointers at the
new dedicated doc + the compose templates. Avoids drift
between two places documenting the same thing.
Why this shape (instead of in-binary TLS):
- Reverse proxies are the production-grade answer anyway.
- Caddy does Let's Encrypt better than we ever would; we don't
have to chase rcgen / instant-acme / rustls CVEs.
- Scope discipline — TLS termination is infrastructure, not
ai-memory's job. Bundling them would repeat cognee's
LiteLLM mistake at a smaller scale.
- The four "thinking you're secure when you're not" failure
modes all become the proxy's problem, not ours: silent ACME
expiry, trust-store dance, cert reload races, fail-open
fallback.
No behavior change for existing single-user installs.
720/720 tests pass.
128 lines
5.0 KiB
YAML
128 lines
5.0 KiB
YAML
# docker compose template — ai-memory + Caddy TLS-terminating reverse proxy.
|
|
#
|
|
# Three usable variants, all in this one file. Pick one and either:
|
|
# - Copy this file, prune the variants you don't want, then
|
|
# `docker compose -f compose.tls.caddy.yml up -d`, or
|
|
# - Use it verbatim and switch via the `Caddyfile` you put next to
|
|
# it. The compose service definitions are the same across variants.
|
|
#
|
|
# Adjacent files this compose expects:
|
|
# ./Caddyfile — your reverse-proxy config; see variants below
|
|
# ./.env.production — AI_MEMORY_AUTH_TOKEN + AI_MEMORY_ALLOWED_HOSTS +
|
|
# your LLM provider creds. NOT committed.
|
|
#
|
|
# Walkthrough + variant explanations + client-side install:
|
|
# docs/https-via-proxy.md
|
|
|
|
name: ai-memory-tls
|
|
|
|
services:
|
|
ai-memory:
|
|
image: akitaonrails/ai-memory:latest
|
|
container_name: ai-memory
|
|
restart: unless-stopped
|
|
# No host port. Caddy reaches ai-memory over the internal docker
|
|
# network; the only inbound surface is Caddy's 80/443 below.
|
|
expose:
|
|
- "49374"
|
|
volumes:
|
|
- ai-memory-data:/data
|
|
env_file:
|
|
- .env.production
|
|
environment:
|
|
- RUST_LOG=ai_memory=info,ai_memory_store=info,ai_memory_wiki=info,ai_memory_mcp=info,tracing_appender=warn
|
|
healthcheck:
|
|
test: ["CMD", "/usr/local/bin/ai-memory", "status"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 5s
|
|
|
|
caddy:
|
|
image: caddy:2-alpine
|
|
container_name: ai-memory-caddy
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- ai-memory
|
|
ports:
|
|
# 80 is needed for Let's Encrypt's HTTP-01 challenge during issuance
|
|
# and renewal. Drop it when using internal-CA mode (Variant B) since
|
|
# there's no ACME to solve.
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
# Persist Caddy state across recreates. caddy-data holds the
|
|
# account key and issued certs — back this up; losing it forces a
|
|
# re-issuance and (in LE's case) brief unavailability.
|
|
- caddy-data:/data
|
|
- caddy-config:/config
|
|
# Only mount this for Variant C (external certs):
|
|
# - /your/cert/path:/etc/caddy/certs:ro
|
|
|
|
volumes:
|
|
ai-memory-data:
|
|
name: ai-memory-data
|
|
caddy-data:
|
|
caddy-config:
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Variant A — Public domain + Let's Encrypt
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
#
|
|
# Put this in ./Caddyfile next to this compose file:
|
|
#
|
|
# memory.example.com {
|
|
# reverse_proxy ai-memory:49374
|
|
# }
|
|
#
|
|
# Requires:
|
|
# - A DNS A or AAAA record for memory.example.com → your public IP
|
|
# - Port 80 + 443 reachable from the internet (for ACME challenges + serving)
|
|
#
|
|
# Caddy issues + auto-renews from Let's Encrypt with no operator
|
|
# action after first start. Browser-trusted by every client out of
|
|
# the box.
|
|
#
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Variant B — LAN-only + Caddy's internal CA
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
#
|
|
# Put this in ./Caddyfile:
|
|
#
|
|
# {
|
|
# local_certs
|
|
# }
|
|
#
|
|
# homelab.local, 192.168.1.50 {
|
|
# reverse_proxy ai-memory:49374
|
|
# }
|
|
#
|
|
# List every hostname + IP a client will use in the site address.
|
|
# Caddy puts them all in the cert's SAN.
|
|
#
|
|
# Requires:
|
|
# - One-time install of Caddy's root cert into each client OS trust
|
|
# store. Pull it with:
|
|
# docker compose exec caddy cat /data/caddy/pki/authorities/local/root.crt > caddy-root.crt
|
|
# Then follow the per-OS install in docs/https-via-proxy.md.
|
|
#
|
|
# Skipping that step gives you security theatre, not security. See
|
|
# the doc for why.
|
|
#
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Variant C — External cert files
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
#
|
|
# Put this in ./Caddyfile:
|
|
#
|
|
# memory.example.com {
|
|
# tls /etc/caddy/certs/memory.crt /etc/caddy/certs/memory.key
|
|
# reverse_proxy ai-memory:49374
|
|
# }
|
|
#
|
|
# Uncomment the cert volume mount in the caddy service above.
|
|
# Caddy hot-reloads certs on file change — no compose restart needed
|
|
# at renewal time.
|