Skip to content

feat: support deployment behind a reverse proxy under a sub-path#9306

Open
Pfannkuchensack wants to merge 4 commits into
invoke-ai:mainfrom
Pfannkuchensack:fix/proxy_usage
Open

feat: support deployment behind a reverse proxy under a sub-path#9306
Pfannkuchensack wants to merge 4 commits into
invoke-ai:mainfrom
Pfannkuchensack:fix/proxy_usage

Conversation

@Pfannkuchensack

Copy link
Copy Markdown
Collaborator

Summary

The web frontend derived all URLs from window.location.origin and used hardcoded paths (/api, /ws/socket.io, /locales, /openapi.json), so running Invoke behind a reverse proxy under a sub-path (e.g. https://host/invoke/) broke because the prefix was lost.

Frontend: adds a shared helper (common/util/baseUrl.ts) that auto-detects the deployment prefix from the entry bundle URL (import.meta.url), relying on Vite's existing base: './'. getBaseUrl(), the socket.io path, the i18n loadPath, the openapi fetch and the React Router basename now all use it. At the domain root the prefix is empty, so behavior is byte-identical to before. Covered by unit tests.

Backend: adds an opt-in base_url setting (plus forwarded_allow_ips). When set, the app is wrapped in SubPathASGIMiddleware, which strips the prefix from the request path and advertises root_path, handling both proxy styles (preserve and strip). uvicorn's root_path is intentionally not used, because it prepends the prefix to the request path and breaks the preserve case (the prefix would appear twice). Default (unset) leaves existing installs completely unchanged.

Two deployment scenarios are supported:

  • Strip proxy (proxy removes the sub-path before forwarding): works with zero backend config via frontend auto-detection. Setting base_url is optional but recommended so /docs + openapi URLs are correct.
  • Preserve proxy (proxy forwards the sub-path unchanged): requires base_url so the middleware can strip the prefix for routing.

QA Instructions

Tested end-to-end with Docker + a Caddy reverse proxy in three configurations; every endpoint was checked for the correct content type and body (not just status code):

Configuration UI API (real JSON) openapi.json locales /docs WebSocket
Preserve proxy + base_url=/invoke ✅ (refs /invoke/openapi.json) ✅ (valid sid)
Strip proxy + base_url=/invoke
Strip proxy, zero-config (base_url unset)

The frontend prefix-detection logic is also covered by unit tests (baseUrl.test.ts).

To reproduce locally (production build required — the dev server has no /assets/ segment, so auto-detection is a no-op there):

Strip proxy (Caddy):

:8080 {
    handle_path /invoke/* {
        reverse_proxy 127.0.0.1:9090
    }
    redir /invoke /invoke/ 308
}

Leave base_url unset (or set base_url: /invoke to also fix /docs).

Preserve proxy (nginx):

location /invoke/ {
    proxy_pass http://127.0.0.1:9090/invoke/;   # note: NO trailing-slash stripping
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}
location = /invoke { return 301 /invoke/; }

Set base_url: /invoke and forwarded_allow_ips: <proxy-ip> in invokeai.yaml.

Then browse http://localhost:8080/invoke/ and confirm: UI loads, generations stream progress (websocket), language switching loads locales, and a reload restores client state. Also verify a regression check at the domain root (http://localhost:9090/) still works with base_url unset.

Merge Plan

Normal merge. No DB schema or redux slice changes. The config schema gains two opt-in fields (base_url, forwarded_allow_ips); both default to a no-op, so no migration is needed.

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Tests added / updated (if applicable)
  • ❗Changes to a redux slice have a corresponding migration
  • Documentation added / updated (if applicable)
  • Updated What's New copy (if doing a release after this PR)

The web frontend derived all URLs from window.location.origin and hardcoded
paths (/api, /ws/socket.io, /locales, /openapi.json), so running Invoke behind
a reverse proxy under a sub-path (e.g. https://host/invoke/) broke because the
prefix was lost.

Frontend: add a shared helper (common/util/baseUrl.ts) that auto-detects the
deployment prefix from the entry bundle URL (import.meta.url), relying on Vite's
existing `base: './'`. getBaseUrl(), the socket.io path, i18n loadPath, the
openapi fetch and the React Router basename now all use it. At the domain root
the prefix is empty, so behavior is byte-identical to before. Covered by unit
tests.

Backend: add an opt-in `base_url` setting (plus `forwarded_allow_ips`). When set,
the app is wrapped in SubPathASGIMiddleware which strips the prefix from the
request path and advertises root_path, handling both proxy styles (preserve and
strip). uvicorn's root_path is intentionally not used, as it prepends the prefix
and breaks the preserve case. Default (unset) leaves existing installs unchanged.

Tested end-to-end with Docker + Caddy for preserve, strip, and zero-config strip.
@github-actions github-actions Bot added python PRs that change python files services PRs that change app services frontend PRs that change frontend files labels Jun 26, 2026
@github-actions github-actions Bot added the docs PRs that change docs label Jun 26, 2026
@lstein lstein self-assigned this Jun 28, 2026
@lstein lstein added the 6.14.x label Jun 28, 2026
@lstein lstein moved this to 6.14.x Theme: USER EXPERIENCE in Invoke - Community Roadmap Jun 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.14.x docs PRs that change docs frontend PRs that change frontend files python PRs that change python files services PRs that change app services

Projects

Status: 6.14.x Theme: USER EXPERIENCE

Development

Successfully merging this pull request may close these issues.

2 participants