Skip to content

Reverse Proxy (URL Path Prefix)

Serve Potato under a sub-path behind a reverse proxy, such as https://host/app1/. Configure a deployment URL prefix so static assets, annotation actions, and live streams resolve correctly under the mount path.

Potato can run behind a reverse proxy under a URL sub-path such as https://host/app1/, which is common when several internal servers share one public HTTPS endpoint:

text
https://host/app1/  ->  http://127.0.0.1:8000/
https://host/app2/  ->  http://127.0.0.1:8001/

The annotation UI loads static assets and performs actions through root-relative URLs (/static/..., /updateinstance, /annotate, /media/...). When Potato is mounted under /app1, those URLs would otherwise resolve against the public root, which shows up as 404s on CSS and JS, a hidden interface, or autosaves failing with "annotations not saved." A deployment prefix fixes this without per-site nginx hacks.

How it works

Both options below set the WSGI SCRIPT_NAME, which Potato reads as the single source of truth for server-rendered url_for(...) output and for the client-side prefix exposed to the browser as window.config.url_prefix. That prefix wraps fetch(), sendBeacon(), EventSource, and root-relative href/action/src attributes. When no prefix is set, SCRIPT_NAME is empty and nothing changes, so this is a no-op for ordinary potato start runs.

Option A — POTATO_PROXY_FIX (proxy sends a forwarded header)

Use this when you control the proxy and it can send forwarded headers. Potato enables Werkzeug's ProxyFix, which reads X-Forwarded-Prefix (and -Proto/-Host/-For) per request.

bash
export POTATO_PROXY_FIX=1
potato start config.yaml -p 8000

nginx, stripping the prefix and forwarding it as a header:

nginx
location /app1/ {
    proxy_pass         http://127.0.0.1:8000/;   # trailing slash strips /app1/
    proxy_set_header   Host              $host;
    proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Proto $scheme;
    proxy_set_header   X-Forwarded-Prefix /app1;
}

ProxyFix trusts forwarded headers. Only enable POTATO_PROXY_FIX when the app is reachable exclusively through the trusted proxy. If the internal port is also directly reachable, a client could spoof X-Forwarded-Prefix or -Host and poison generated URLs.

Option B — POTATO_URL_PREFIX (proxy config cannot change)

Use this when you cannot add forwarded headers but you know the public mount path. Potato injects the prefix into SCRIPT_NAME itself.

bash
export POTATO_URL_PREFIX=/app1
potato start config.yaml -p 8000

The proxy must still strip the prefix before forwarding, so Flask receives unprefixed paths such as /static/styles.css:

nginx
location /app1/ {
    proxy_pass       http://127.0.0.1:8000/;     # trailing slash strips /app1/
    proxy_set_header Host $host;
}

If both variables are set, the per-request forwarded prefix wins and POTATO_URL_PREFIX is the fallback.

Live streaming (Server-Sent Events)

The live-agent and live-coding viewers use SSE. The URL prefix is applied automatically, but SSE also needs the proxy to disable buffering on the stream location, or events are held back:

nginx
location /app1/api/ {
    proxy_pass            http://127.0.0.1:8000/api/;
    proxy_set_header      Host $host;
    proxy_buffering       off;
    proxy_read_timeout    3600s;
}

Verifying

  1. Load https://host/app1/ and confirm CSS and JS load with no 404s.
  2. Make an annotation and confirm it autosaves.
  3. Navigate Next/Previous and confirm media and data render.
  4. If using live agent evaluation, confirm the stream connects and updates.

Notes and limitations

  • Root-relative links inside displayed annotation content are also prefixed. Content authors who mean to point at the public root should use absolute URLs.
  • pip-installed deployments rely on packaged static assets; ensure your build includes the nested static/ directories.

For implementation details, see the source documentation.