Skip to main content
The official image runs the frontend and the backend behind its own nginx on one port, so a proxy in front of it has a single upstream and nothing to route. This page is for the other cases: the frontend and backend run as separate services (from source, or from images you build), or the backend needs a hostname of its own, which MCP sign-in clients do.

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:
/auth and /integrations belong to the frontend. They look like backend paths, but they are the sign-in screens and the page every network returns to after authorization. Sending them to the backend leaves you with no login page and a failed connect on every network. The browser reaches the backend’s own routes through NEXT_PUBLIC_BACKEND_URL, which is why they carry /api.

Uploaded files

With STORAGE_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 /api so the container’s nginx passes it to the backend:
With separate services, send the host straight to the backend on port 3000.
2

Advertise it

MCP’s OAuth discovery then names this host for the resource, the issuer and the token and registration endpoints. The web app keeps using NEXT_PUBLIC_BACKEND_URL and does not change. Recreate the container after the change.
The sign-in address for MCP clients is then 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:
Last modified on September 23, 2026