Files
ai-memory/docker/compose.tls.cloudflared.yml
T
AkitaOnRails 1c8bb1aa18 docs: HTTPS-via-proxy guide + compose templates + sharpened startup warning
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.
2026-05-30 12:36:21 -03:00

107 lines
4.6 KiB
YAML

# docker compose template — ai-memory + Cloudflare Tunnel.
#
# Outbound-only tunnel from the homelab to Cloudflare's edge. TLS is
# terminated at the edge with a Cloudflare cert (universally trusted
# everywhere). No ports exposed on the host, no public IP needed,
# no DNS records to set up (Cloudflare manages the CNAME).
#
# Pairs especially well with the multi-user homelab case from
# docs/users.md — every per-user token rides encrypted from each
# client through Cloudflare to the tunnel.
#
# Prerequisites (one-time, in the Cloudflare dashboard):
# 1. Have a domain on Cloudflare (DNS hosted there; registrar
# doesn't matter).
# 2. Zero Trust → Networks → Tunnels → Create a tunnel → name it
# `ai-memory-homelab` → save.
# 3. Copy the token Cloudflare gives you into .env as
# CLOUDFLARE_TUNNEL_TOKEN.
# 4. In the tunnel's "Public hostnames" tab: add `memory.example.com`
# pointing at service `http://ai-memory:49374`.
# 5. (Optional, recommended) Wrap that hostname in a Cloudflare
# Access application for SSO on top of ai-memory's bearer auth.
#
# Adjacent files this compose expects:
# ./.env.production — AI_MEMORY_AUTH_TOKEN +
# AI_MEMORY_ALLOWED_HOSTS +
# CLOUDFLARE_TUNNEL_TOKEN. NOT committed.
#
# Walkthrough + client-side install:
# docs/https-via-proxy.md
name: ai-memory-cloudflared
services:
ai-memory:
image: akitaonrails/ai-memory:latest
container_name: ai-memory
restart: unless-stopped
# No host port. The tunnel reaches ai-memory over the internal
# docker network — nothing inbound from the public internet.
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
cloudflared:
image: cloudflare/cloudflared:latest
container_name: ai-memory-tunnel
restart: unless-stopped
depends_on:
- ai-memory
command: tunnel --no-autoupdate run
# `cloudflared` exits with code 1 on token rejection, which the
# `restart: unless-stopped` policy will loop on. Watch the logs
# the first time you start it — `docker compose logs -f cloudflared`
# — to confirm registration succeeded.
environment:
- TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
volumes:
ai-memory-data:
name: ai-memory-data
# ──────────────────────────────────────────────────────────────────────
# .env.production minimum content (DO NOT COMMIT THIS FILE):
# ──────────────────────────────────────────────────────────────────────
#
# AI_MEMORY_AUTH_TOKEN=<long-random-token-from-`ai-memory generate-auth-token`>
# AI_MEMORY_ALLOWED_HOSTS=memory.example.com,localhost,127.0.0.1
# AI_MEMORY_BIND=0.0.0.0:49374
#
# CLOUDFLARE_TUNNEL_TOKEN=eyJ...<long-base64-from-the-cf-dashboard>...
#
# # Optional LLM provider — see docker-compose.yml for the catalogue.
# # AI_MEMORY_LLM_PROVIDER=anthropic
# # ANTHROPIC_API_KEY=sk-ant-...
#
# `AI_MEMORY_ALLOWED_HOSTS` MUST include the public hostname that
# Cloudflare forwards under, or ai-memory's DNS-rebinding guard will
# reject the tunneled requests.
#
# ──────────────────────────────────────────────────────────────────────
# Client config after the tunnel is up
# ──────────────────────────────────────────────────────────────────────
#
# ai-memory install-mcp --client claude-code --apply \
# --server-url "https://memory.example.com/mcp" \
# --auth-token "$AI_MEMORY_AUTH_TOKEN"
#
# ai-memory install-hooks --agent claude-code --apply \
# --server-url "https://memory.example.com" \
# --auth-token "$AI_MEMORY_AUTH_TOKEN"
#
# (Substitute codex / cursor / gemini-cli / antigravity / opencode /
# omp / openclaw — same shape, see docs/install.md for the catalogue.)