Frontend and backend on one host
The frontend listens on port 4200 and the backend on port 3000. Route by path:
Then set:
BACKEND_INTERNAL_URL is how the frontend’s server reaches the backend directly, so it is the backend’s address on your internal network, never the public one.
The backend can also run on a host of its own, with NEXT_PUBLIC_BACKEND_URL=https://api.postqueen.example.com and no path. Keep both hosts under the same parent domain: the login cookie is set for the parent domain of FRONTEND_URL, so postqueen.example.com and api.postqueen.example.com share it.
A Caddy example of the same split:
Uploaded files
WithSTORAGE_PROVIDER=local, the frontend serves /uploads/... itself: it passes each request to its own route, which reads the file from UPLOAD_DIRECTORY. So the frontend needs the uploads folder mounted as well as the backend, at the same path. With Cloudflare R2, files come from the bucket’s address and neither service needs the folder.
Give the backend its own host
MCP clients that sign in with OAuth look for the authorization server’s metadata at the root of its host (RFC 8414). When the backend lives under/api, that lookup lands on the frontend and the sign-in stops. The fix is a hostname that serves the backend at its root, for example api.postqueen.example.com:
1
Route the new host to the backend
With the official image, prefix every request with With separate services, send the host straight to the backend on port 3000.
/api so the container’s nginx passes it to the backend:2
Advertise it
NEXT_PUBLIC_BACKEND_URL and does not change. Recreate the container after the change.https://api.postqueen.example.com/mcp-oauth-dynamic. MCP on your server covers the rest.
Routes other services call
These arrive from outside the browser, all on the backend. With the image or the/api split they sit under /api/, so the rules above already cover them:
Related
- Domain and HTTPS
- Nginx and Traefik
- Development environment: running the two services from source
- Configuration reference