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:
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.
export POTATO_PROXY_FIX=1
potato start config.yaml -p 8000nginx, stripping the prefix and forwarding it as a header:
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.
export POTATO_URL_PREFIX=/app1
potato start config.yaml -p 8000The proxy must still strip the prefix before forwarding, so Flask receives unprefixed paths such as /static/styles.css:
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:
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
- Load
https://host/app1/and confirm CSS and JS load with no 404s. - Make an annotation and confirm it autosaves.
- Navigate Next/Previous and confirm media and data render.
- 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 nestedstatic/directories.
Related
- Production Setup — HTTPS and process management
- Live Agent Evaluation — the SSE viewers affected by buffering
For implementation details, see the source documentation.