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 :8080Do 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 CaddyfileTraefik (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=80nginx (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
| Setting | Bundled value | Why it matters |
|---|---|---|
| Body size | client_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 timeouts | 120 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
8080is not exposed to the internet TM_JWT_SECRETchanged andTM_ALLOW_INSECURE_JWTunset/falseTM_CORS_ORIGINSleft empty (same-origin — the proxy keeps one origin)- Outer proxy body size ≥ 20 MB and timeouts ≥ 120 s