TraderMemos
Self-hosting

Reverse proxy & TLS

Copy-paste Caddy, Traefik, and nginx configs to put TraderMemos behind HTTPS.

The Docker stack serves plain HTTP on port 3000. For anything reachable beyond localhost, put a TLS-terminating proxy in front and point it at the web service only — /api, /docs, and /healthz are already proxied to the API by the bundled nginx, so everything stays same-origin and no CORS is needed.

Browser ──HTTPS──► Caddy / Traefik / nginx ──HTTP :3000──► web (nginx) ──► api :8080

Do not expose the API port (8080) publicly unless you have a reason to; you can remove its port mapping from Compose entirely.

Caddy (simplest)

Caddyfile — automatic Let's Encrypt certificates:

journal.example.com {
    reverse_proxy localhost:3000
}
caddy run --config Caddyfile

Traefik (Docker labels)

Add labels to the web service in a Compose override:

services:
  web:
    labels:
      - traefik.enable=true
      - traefik.http.routers.tradermemos.rule=Host(`journal.example.com`)
      - traefik.http.routers.tradermemos.entrypoints=websecure
      - traefik.http.routers.tradermemos.tls.certresolver=letsencrypt
      - traefik.http.services.tradermemos.loadbalancer.server.port=80

nginx (host-level)

server {
    listen 443 ssl;
    server_name journal.example.com;

    ssl_certificate     /etc/letsencrypt/live/journal.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/journal.example.com/privkey.pem;

    client_max_body_size 20m;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 120s;
        proxy_send_timeout 120s;
    }
}

Limits and timeouts to keep aligned

SettingBundled valueWhy it matters
Body sizeclient_max_body_size 20m (web nginx)Must be ≥ the API upload caps (TM_*_MAX_BYTES, default 10 MiB each). If you raise the caps past 20 MiB, raise it here and in your outer proxy
Proxy timeouts120 s read/send (web nginx)AI screenshot scans can take up to TM_OCR_VISION_TIMEOUT_SEC (default 90 s). An outer proxy with a 60 s default will cut those requests — set it to ≥ 120 s

Checklist

  • HTTPS terminates at your proxy; only port 3000 (web) is forwarded
  • API port 8080 is not exposed to the internet
  • TM_JWT_SECRET changed and TM_ALLOW_INSECURE_JWT unset/false
  • TM_CORS_ORIGINS left empty (same-origin — the proxy keeps one origin)
  • Outer proxy body size ≥ 20 MB and timeouts ≥ 120 s

On this page