Deployment
Recto runs as a set of Docker containers orchestrated by Docker Compose. Caddy serves as the single entry point — handling TLS, routing, and web UI authentication.
Architecture
Section titled “Architecture”All traffic flows through Caddy on ports 80/443:
| Path | Destination | Auth |
|---|---|---|
/mcp, /mcp/* | MCP server | API key or OAuth |
/api, /api/* | REST API (strips /api prefix) | Basic auth (Caddy injects API key) |
/.well-known/oauth-authorization-server | API (OAuth discovery) | None |
/authorize, /token, /register | API (OAuth endpoints) | None |
/* | Web dashboard (static files) | Basic auth |
Deployment Options
Section titled “Deployment Options”Pre-built images (recommended)
Section titled “Pre-built images (recommended)”Uses pre-built images from GitHub Container Registry — no build step required:
docker compose -f docker-compose.ghcr.yml up -dBuild from source
Section titled “Build from source”Builds all images locally:
docker compose up -dDeployment Scenarios
Section titled “Deployment Scenarios”Local development
Section titled “Local development”RECTO_DOMAIN=localhostCaddy generates a self-signed certificate for localhost. Your browser will show a security warning — accept it. Everything works except OAuth-based MCP clients (like Claude Desktop), which require a trusted certificate.
Production (Caddy as the edge)
Section titled “Production (Caddy as the edge)”RECTO_DOMAIN=recto.example.comCaddy automatically provisions a Let’s Encrypt TLS certificate. Ports 80 and 443 must be open and reachable from the internet. Everything works, including OAuth for Claude Desktop.
Behind a TLS-terminating proxy (ngrok, Cloudflare, AWS ALB)
Section titled “Behind a TLS-terminating proxy (ngrok, Cloudflare, AWS ALB)”RECTO_DOMAIN=your-domain.ngrok-free.appCADDY_GLOBAL_OPTIONS=auto_https offThe external proxy handles TLS. Caddy serves plain HTTP on port 80. Set RECTO_DOMAIN to the hostname only (no https:// prefix).
For ngrok testing:
ngrok http 80# Copy the hostname (e.g., abc123.ngrok-free.app)# Set RECTO_DOMAIN=abc123.ngrok-free.app in .env# Set CADDY_GLOBAL_OPTIONS=auto_https off in .envdocker compose up -d --force-recreate api proxySecurity Considerations
Section titled “Security Considerations”Firewall direct ports
Section titled “Firewall direct ports”The API (3000), MCP (3001), and database (5432) ports are exposed on the host for convenience. In production, firewall these ports so that all traffic goes through Caddy on ports 80/443. Only Caddy enforces basic auth for the web UI.
API key injection
Section titled “API key injection”The web dashboard’s /api routes are behind basic auth. Caddy automatically injects the RECTO_API_KEY as a Bearer token when proxying requests to the API. The API key is never exposed to the browser.
Disabling the web dashboard
Section titled “Disabling the web dashboard”If you only use Recto through MCP (e.g., Claude Desktop), the web dashboard is unnecessary. Disabling it removes the basic auth attack surface — basic auth is vulnerable to brute force and doesn’t support multi-factor authentication.
1. Replace the Caddyfile catch-all handler
Change the last handle block in your Caddyfile from serving static files to returning a 404:
handle { respond 404}The full Caddyfile should look like this:
{$CADDY_GLOBAL_OPTIONS}
{$RECTO_DOMAIN:localhost} { handle /mcp { reverse_proxy mcp:3001 }
handle /mcp/* { reverse_proxy mcp:3001 }
handle /.well-known/oauth-authorization-server { reverse_proxy api:3000 }
handle /authorize { reverse_proxy api:3000 }
handle /token { reverse_proxy api:3000 }
handle /register { reverse_proxy api:3000 }
handle { respond 404 }}2. Skip the web container
Start the stack without building or running the web service:
# Pre-built imagesdocker compose -f docker-compose.ghcr.yml up -d --scale web=0
# Build from sourcedocker compose up -d --scale web=0You can also remove RECTO_WEB_USER and RECTO_WEB_PASSWORD_HASH from your .env since they are no longer used.
What you lose: The web dashboard (timeline, search, tags, and settings pages). All MCP and API functionality continues to work normally.
Changing the API key
Section titled “Changing the API key”After updating RECTO_API_KEY in .env, restart the proxy to pick up the new value:
docker compose up -d --force-recreate proxy