Reverse proxy
Conatus listens on plain HTTP inside its container. To reach it from a domain you terminate TLS at a proxy and forward to the app’s port.
Set both URL variables
Section titled “Set both URL variables”This is the step people miss. Conatus does not guess its own public address. Sign-in callbacks and generated links both come from configuration:
AUTH_URL=https://tasks.example.comPUBLIC_BASE_URL=https://tasks.example.comBoth must be the external HTTPS origin, with no trailing slash. If you set only one, you get a working page whose login redirects land on the wrong host.
Restart after changing them:
docker compose up -dBind the app to loopback
Section titled “Bind the app to loopback”Once a proxy is in front, the app should not also be reachable directly. By
default, Compose publishes it on 0.0.0.0:
CONATUS_BIND_ADDRESS=127.0.0.1CONATUS_PORT=4399If the proxy runs in Docker on the same network, drop the published port
entirely and let the proxy reach app:3000 over the Compose network instead.
Caddy gets you certificates with no extra configuration.
tasks.example.com { reverse_proxy 127.0.0.1:4399}If Caddy runs as a Compose service on the same network:
tasks.example.com { reverse_proxy app:3000}server { listen 443 ssl http2; server_name tasks.example.com;
ssl_certificate /etc/letsencrypt/live/tasks.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/tasks.example.com/privkey.pem;
# Attachments are uploaded through the app. client_max_body_size 50m;
location / { proxy_pass http://127.0.0.1:4399; proxy_http_version 1.1; 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_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }}
server { listen 80; server_name tasks.example.com; return 301 https://$host$request_uri;}client_max_body_size matters: nginx’s 1 MB default rejects most file
attachments with a 413 before the app ever sees them.
A second name for the MCP server
Section titled “A second name for the MCP server”If you run the optional MCP sidecar, give it its own hostname rather than a path on the app’s. A typical pair:
https://tasks.example.com→ the web app and/api/v1https://mcp.example.com/mcp→ the MCP sidecar on port 3001
MCP_PUBLIC_URL must be that exact external URL, ending in /mcp. See
MCP server.
Checking it worked
Section titled “Checking it worked”curl -I https://tasks.example.comThen sign out and sign in again through the domain. If you land back on
localhost after login, AUTH_URL is still wrong.
