This guide explains how to deploy Sovereign Brain as an internal-only service, with a trusted client talking to an internal server across a trusted network.
Internal client
-> http://INTERNAL_HOST:80
-> internal edge nginx
-> web
-> api
-> mcp
-> supabase-proxy
-> supabase-auth
-> db
The important point is that everything stays on the private side. There is no public DNS, no TLS termination requirement in this blueprint, and no assumption that anything is internet-facing.
- Docker Engine 24+
- Docker Compose v2+
- an internal server, referenced here as
INTERNAL_HOST - internal clients that can reach
http://INTERNAL_HOST - container images for API, Web, and MCP published somewhere the server can pull from
mkdir -p /opt/sovereign-brain
cd /opt/sovereign-brain
cp .env.example .envEdit .env and replace every placeholder.
At minimum set:
POSTGRES_PASSWORDSUPABASE_JWT_SECRETSUPABASE_ANON_KEYLOCAL_USER_IDandSTATIC_BEARER_TOKENif a trusted local agent will connect over MCP without an interactive Supabase user tokenAPP_URLSUPABASE_URLNEXT_PUBLIC_*API_BASE_IMAGEandAPI_IMAGEWEB_IMAGEMCP_IMAGE
Recommended internal values:
APP_URL=http://INTERNAL_HOSTAPP_URLS=http://INTERNAL_HOST,http://CLIENT_IPSUPABASE_URL=http://INTERNAL_HOSTSUPABASE_AUTH_EXTERNAL_URL=http://INTERNAL_HOST/auth/v1NEXT_PUBLIC_API_URL=http://INTERNAL_HOST/apiNEXT_PUBLIC_SUPABASE_URL=http://INTERNAL_HOSTNEXT_PUBLIC_MCP_URL=http://INTERNAL_HOST/mcpNEXT_PUBLIC_CANONICAL_HOST=INTERNAL_HOSTor your stable internal hostnameNEXT_PUBLIC_CANONICAL_REDIRECT_HOSTS=ALTERNATE_INTERNAL_HOST,OLD_HOSTNAMEfor any alternate addresses that should redirect to the canonical browser origin
Important: SUPABASE_URL and NEXT_PUBLIC_SUPABASE_URL must be the public base origin, for example http://INTERNAL_HOST. Do not include /auth/v1; the clients append that path themselves. SUPABASE_AUTH_EXTERNAL_URL is the GoTrue external auth URL and should include /auth/v1.
Choose one canonical browser origin and stick to it. Browsers isolate auth cookies and local storage by origin, so reaching the same stack as http://INTERNAL_HOST in one tab and http://ALTERNATE_INTERNAL_HOST in another can look like two different sessions or users. Set NEXT_PUBLIC_CANONICAL_HOST to the preferred host and list any old/internal aliases in NEXT_PUBLIC_CANONICAL_REDIRECT_HOSTS.
For MCP clients, configure the client with the MCP URL and a bearer header. Example MCP client shape:
{
"mcpServers": {
"llmwiki": {
"url": "http://INTERNAL_HOST/mcp",
"headers": {
"Authorization": "Bearer replace-with-long-random-mcp-token"
}
}
}
}The static token is only for trusted private-network agent clients. Browser users should still authenticate through GoTrue/Supabase.
Files to review:
infra/nginx/conf.d/llm-wiki.confinfra/supabase/nginx.conf
These are the key files behind the fix.
The edge config handles internal routing for /, /api/, /mcp, and /auth/v1/.
The Supabase proxy config handles auth preflights and prevents duplicate or conflicting CORS headers from leaking through.
docker compose pull
docker compose build api mcp
docker compose up -dAPI_BASE_IMAGE must point at an API image compatible with the current upstream llmwiki API modules. If a published base image lags behind upstream, build or pull the current upstream API image first and use it as API_BASE_IMAGE; the Sovereign Brain overlay then adds hosted auth, graph, websocket, and private-deployment behavior on top.
docker compose ps
docker compose logs -f edge api web mcp supabase-proxy supabase-authThen test from a trusted client:
curl -I http://INTERNAL_HOST/
curl -I http://INTERNAL_HOST/api/health
curl -I http://INTERNAL_HOST/auth/v1/health
# If you configured alternate redirect hosts, this should return 308 to the canonical origin:
curl -I http://ALTERNATE_INTERNAL_HOST/
curl -i -X OPTIONS http://INTERNAL_HOST/auth/v1/token \
-H Origin: http://CLIENT_IP \
-H Access-Control-Request-Method: POSTThat OPTIONS request is the fast sanity check for the original browser failure.
The working configuration depends on a few specifics:
OPTIONSrequests return204before they hit app logic- auth responses do not emit duplicate
Access-Control-Allow-Originheaders - upstream auth headers are forwarded cleanly
X-Forwarded-Proto,X-Forwarded-Host, andHostremain coherent- non-canonical browser origins redirect before the app/auth layer can create a second session
- websocket upgrade headers are preserved where required
If the UI shows Load failed or Unexpected response code, check these proxy rules first.
After the stack is up, create a login-capable first user through GoTrue:
BASE_URL=http://INTERNAL_HOST \
EMAIL=admin@example.com \
PASSWORD='change-me-long-random-password' \
DISPLAY_NAME='Admin' \
./scripts/create-initial-user.shThis uses the public auth signup endpoint, verifies password login, and marks the user onboarded through the API. Set COMPLETE_ONBOARDING=false if you only want to create the login. Once your first private user exists, set GOTRUE_DISABLE_SIGNUP=true in .env and restart with docker compose up -d if you do not want open signup.
- bind exposure to the private interface only if possible
- restrict which internal clients may reach port 80
- keep signups disabled unless you actually need them
- move secrets into a real secret store if this grows beyond a lab setup
- back up Postgres volumes on a schedule
- pin image tags instead of relying on
latest
- collect raw notes, PDFs, transcripts, exports, and markdown
- ingest them through the API or your ingestion pipeline
- run an initial synthesis pass to create wiki pages and entity pages
- point your agent runtime at the MCP endpoint
- make wiki maintenance part of normal agent work
- do not reintroduce public DNS or TLS assumptions into this repo
- do not mix real internal IPs or hostnames into committed files
- do not treat multiple raw IPs as equally valid browser entrypoints; pick one canonical origin and redirect the rest
- do not skip the auth preflight test
- do not point agents only at raw files and call that memory