Skip to content

feat(web-studio): bundle web-studio into docker, serve at /studio#2156

Merged
qin-ctx merged 2 commits into
volcengine:mainfrom
ZaynJarvis:feat/web-studio-docker-bundle
May 21, 2026
Merged

feat(web-studio): bundle web-studio into docker, serve at /studio#2156
qin-ctx merged 2 commits into
volcengine:mainfrom
ZaynJarvis:feat/web-studio-docker-bundle

Conversation

@ZaynJarvis
Copy link
Copy Markdown
Collaborator

Description

Bundles the web-studio Vite SPA into the OpenViking docker image and exposes it at /studio/ on the same OV server origin (port 1933). After this change, running the image is enough to get both the API and the new web frontend on a single port — no separate hosting, no CORS dance, no extra deployment unit.

Type of Change

  • New feature (non-breaking change that adds functionality)
  • Bug fix (non-breaking change that fixes an issue) — same-origin default for ovClient

Changes Made

  • Dockerfile: new web-studio-builder stage (node:20-bookworm-slim) runs npm ci + npm run build -- --base=/studio/ and emits /web-studio/dist. The runtime stage copies /web-studio/dist → /app/web-studio/dist and exports OPENVIKING_WEB_STUDIO_DIR=/app/web-studio/dist so the server picks it up out-of-the-box. The npm install layer is cached on /root/.npm keyed by TARGETPLATFORM, so rebuilds only re-run when package.json / lockfile change.
  • openviking/server/app.py: when OPENVIKING_WEB_STUDIO_DIR (or the in-repo fallback path) contains a built dist/, register /studio and /studio/{path:path} handlers. The latter serves matching asset files when they exist, and falls back to index.html for unknown paths so TanStack Router deep-links survive a hard refresh. index.html is sent with Cache-Control: no-store to avoid stale shells. The legacy console/static favicon directory is replaced by the same web-studio/dist (web-studio now owns the canonical icons), so the top-level /favicon.* and /mcp/favicon.* routes stay backwards-compatible without a separate static folder.
  • web-studio/public/: add favicon.ico, favicon-32.png, apple-touch-icon.png so the build emits the icon set the server expects.
  • web-studio/src/lib/ov-client/client.ts: drop the hardcoded 'http://127.0.0.1:1933' fallback in the module-level ovClient initializer. normalizeBaseUrl already falls back to window.location.origin in browsers, but the literal short-circuited it — so when the SPA was served by OV at /studio/, every API call still tried to hit the user's local 127.0.0.1:1933 and failed. Removing the literal makes the default same-origin, which is the right answer for every "served by OV" shape (docker bundle, port-forwarded docker, reverse-proxied custom domain). The in-app Connection dialog can still override the base URL at runtime (persisted to localStorage), so dev workflows running vite separately can either set VITE_OV_BASE_URL or just punch in the local URL once.

Testing

  • Existing OV server unit tests pass locally (no test surface changed)
  • web-studio npm run build passes
  • End-to-end smoke on a live Railway deploy of the resulting image:
    • GET /health → 200
    • GET /studio/ → 200 (SPA shell)
    • GET /favicon.ico → 200
    • ov add-resource <file> → upload + semantic/embedding queues drain successfully
    • ov add-memory '<text>' → 200, retrievable via ov find
    • Loading /studio/ in a browser issues API calls back to the deployed origin (same-origin), not localhost
  • Tested on platforms:
    • Linux (CI / Railway)
    • macOS (local docker build)

Additional Notes

  • Outside docker, the server falls back to <repo>/web-studio/dist so npm run build inside web-studio/ is enough to enable /studio/ locally without setting any env var.
  • This is intentionally minimal — no changes to existing console routes; the legacy /console static path is untouched. A follow-up PR can deprecate / remove openviking/console/static once the team is comfortable.

alice and others added 2 commits May 21, 2026 12:35
…avicon to public

- Dockerfile: add node:20 build stage for web-studio, `vite build --base=/studio/`
  output to /app/web-studio/dist. New ENV OPENVIKING_WEB_STUDIO_DIR.
- server/app.py: serve /studio and /studio/{path:path} from dist; SPA deep-link
  fallback to index.html. Favicon routes (/favicon.ico, /favicon.png,
  /apple-touch-icon.png, /mcp/favicon.ico+png+touch) read from the same dist —
  no more separate console/static dependency.
- web-studio/public: ship favicon.ico, favicon-32.png, apple-touch-icon.png
  inside the SPA bundle so they end up in dist root.

Old console (8020) untouched in this branch.
…efault

ovClient was initialized with `baseUrl: ENV_BASE_URL || 'http://127.0.0.1:1933'`,
which short-circuited normalizeBaseUrl's window.location.origin fallback. As a
result, when web-studio was served by the OV server itself (e.g. bundled in the
OV docker image at /studio/), the SPA would try to call back to the user's local
127.0.0.1:1933 instead of the same origin it was loaded from — making the
bundled deployment unusable.

Removing the literal fallback lets normalizeBaseUrl pick window.location.origin
when ENV_BASE_URL is empty, which is the right default for every "served by OV"
shape (bundled image, port-forwarded docker, reverse-proxied custom domain).
Dev workflows running vite separately can opt in by setting VITE_OV_BASE_URL or
overriding the URL in the in-app Connection dialog (which persists to
localStorage).
@github-actions
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🏅 Score: 78
🧪 No relevant tests
🔒 No security concerns identified
✅ No TODO sections
🔀 Multiple PR themes

Sub-PR theme: Add web-studio build stage to Dockerfile

Relevant files:

  • Dockerfile

Sub-PR theme: Serve web-studio SPA and handle favicons from web-studio dist

Relevant files:

  • openviking/server/app.py

Sub-PR theme: Update ovClient default baseUrl to use ENV_BASE_URL only

Relevant files:

  • web-studio/src/lib/ov-client/client.ts

⚡ Recommended focus areas for review

Favicon Routes Regression

The favicon routes are now only registered when the web-studio dist directory exists and contains all required files. This breaks backwards compatibility for setups that don't use the web-studio bundle, as the favicon routes are no longer available at all.

_favicon_source = _studio_dir if _studio_dir.is_dir() else None
if _favicon_source is not None and all(
    (_favicon_source / fname).is_file() for fname, _ in _favicon_files.values()
):

    def _make_favicon_handler(filename: str, media_type: str):
        path = _favicon_source / filename

        async def _handler():
            return FileResponse(path, media_type=media_type, headers=_favicon_headers)

        return _handler

    for _route, (_fname, _mime) in _favicon_files.items():
        app.add_api_route(_route, _make_favicon_handler(_fname, _mime), include_in_schema=False)
Missing Base URL Fallback

Removed the default fallback to http://127.0.0.1:1933 for the ovClient baseUrl. If ENV_BASE_URL is not set (e.g., in local development when running web-studio separately via Vite dev), the baseUrl becomes undefined, potentially breaking API connectivity.

export const ovClient = createOvClient({
  baseUrl: ENV_BASE_URL,
  bindSdkClient: true,
})

@github-actions
Copy link
Copy Markdown

PR Code Suggestions ✨

No code suggestions found for the PR.

@qin-ctx qin-ctx merged commit cb8e1e6 into volcengine:main May 21, 2026
5 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in OpenViking project May 21, 2026
ZaynJarvis added a commit to ZaynJarvis/OpenViking that referenced this pull request May 21, 2026
…o in pip, fix favicons

The OpenViking docker image still launched the legacy `openviking/console`
standalone service on port 8020. Now that web-studio is bundled into the OV
server itself at /studio (see volcengine#2156), that process is redundant and the
port is just a confusing artefact.

This change retires the old console (python package + 8020 + console-frontend
favicons) but **keeps the in-compose Caddy as a stable single-ingress on
port 1934**, just simplified to one upstream now that there's no 8020. The
server-side BFF at `openviking/server/routers/console.py` (under
`/api/v1/console/*`) is also kept — web-studio uses the same endpoints.

**The OAuth authorize page (`openviking/server/oauth/router.py`) is
deliberately untouched in this PR** — the console-link button and Quick
authorize same-origin panel will be re-pointed at web-studio in a focused
follow-up.

BREAKING CHANGES:
- Port 8020 is gone from the docker image and docker-compose.yml; Caddy at
  1934 now forwards everything to 1933 (web-studio lives at /studio there).
  Anything bookmarked at `http://host:8020/...` must migrate to
  `http://host:1933/studio/`.
- `python -m openviking.console.bootstrap` no longer exists; the python
  package `openviking.console` has been removed.

Pip packaging:
- web-studio dist is now shipped inside the wheel under
  `openviking/web_studio/dist/` (mirroring the old `openviking/console/static/`
  layout). The dockerfile copies `--from=web-studio-builder /web-studio/dist`
  into the source tree before `uv sync`, so the wheel produced by the
  default docker build always carries the SPA. Building the wheel without
  running `npm run build` first leaves the directory empty, which gracefully
  degrades /studio to a 404 without breaking server startup.
- Favicon assets (`favicon.ico` / `favicon-32.png` / `apple-touch-icon.png`,
  ~11 KB total) are duplicated into `openviking/server/static/` and shipped
  via package-data so `/favicon.*` and `/mcp/favicon.*` routes are always
  registered, regardless of whether the web-studio dist is bundled.
- `pyproject.toml` and `setup.py` `package-data` drop `console/static/**`
  and add `server/static/**` + `web_studio/dist/**`.
- New favicons (the 16/32/180 set in both `openviking/server/static/` and
  `web-studio/public/`) are downscaled from the canonical
  `web-studio/public/openviking-icon.png`, so the small-icon family matches
  the SPA's high-res rel="icon" target — the studio tab icon now stays
  consistent whether the browser uses the HTML link tag or falls back to
  auto-fetching `/favicon.ico`.

Server:
- `openviking/server/app.py` now reads `/studio` from
  `Path(__file__).parent.parent / 'web_studio' / 'dist'` by default;
  `OPENVIKING_WEB_STUDIO_DIR` still wins for dev mode pointing at a
  repo-local build. Favicon routes are unconditionally registered and
  load from `openviking/server/static/`.
- `openviking/observability/usage_audit/projection.py` drops the legacy
  `/console/*` skip prefix (the BFF prefix `/api/v1/console/*` remains).

Docker:
- `web-studio-builder` stage moved earlier (Stage 2) so its dist can flow
  into `py-builder` before `uv sync` runs.
- Runtime stage no longer separately copies the dist or sets
  `OPENVIKING_WEB_STUDIO_DIR`; the in-package path is the default.
- Entrypoint renamed `openviking-console-entrypoint.sh` -> `openviking-entrypoint.sh`
  and stripped of the `python -m openviking.console.bootstrap` launch.
- `EXPOSE 1933 8020` -> `EXPOSE 1933`.
- `docker-compose.yml` drops the openviking service's 8020 port mapping;
  the caddy service stays but no longer needs port 8020 exposed.
- `Caddyfile` simplified to a single `:1934 { reverse_proxy openviking:1933 }`
  — the legacy `/console/*` route to :8020 is gone.

Docs:
- en/zh quickstart updated to drop the 8020 mapping and explain that the
  API server now also serves `/studio`.
- Other guides (`12-public-access.md`, `11-oauth.md`, `05-observability.md`,
  `04-setup-for-agent.md`, `03-deployment.md`) are intentionally left for a
  focused follow-up PR alongside the OAuth quick-authorize reintroduction.

Tests:
- Deleted `tests/misc/test_console_{proxy,static_assets}.py` (covered the
  removed console package). `tests/observability/test_console_router.py`
  stays — it covers the BFF, which remains.
ZaynJarvis added a commit to ZaynJarvis/OpenViking that referenced this pull request May 21, 2026
…o in pip, fix favicons

The OpenViking docker image still launched the legacy `openviking/console`
standalone service on port 8020. Now that web-studio is bundled into the OV
server itself at /studio (see volcengine#2156), that process is redundant and the
port is just a confusing artefact.

This change retires the old console (python package + 8020 + console-frontend
favicons) but **keeps the in-compose Caddy as a stable single-ingress on
port 1934**, just simplified to one upstream now that there's no 8020. The
server-side BFF at `openviking/server/routers/console.py` (under
`/api/v1/console/*`) is also kept — web-studio uses the same endpoints.

**The OAuth authorize page (`openviking/server/oauth/router.py`) is
deliberately untouched in this PR** — the console-link button and Quick
authorize same-origin panel will be re-pointed at web-studio in a focused
follow-up.

BREAKING CHANGES:
- Port 8020 is gone from the docker image and docker-compose.yml; Caddy at
  1934 now forwards everything to 1933 (web-studio lives at /studio there).
  Anything bookmarked at `http://host:8020/...` must migrate to
  `http://host:1933/studio/`.
- `python -m openviking.console.bootstrap` no longer exists; the python
  package `openviking.console` has been removed.

Pip packaging:
- web-studio dist is now shipped inside the wheel under
  `openviking/web_studio/dist/` (mirroring the old `openviking/console/static/`
  layout). The dockerfile copies `--from=web-studio-builder /web-studio/dist`
  into the source tree before `uv sync`, so the wheel produced by the
  default docker build always carries the SPA. Building the wheel without
  running `npm run build` first leaves the directory empty, which gracefully
  degrades /studio to a 404 without breaking server startup.
- Favicon assets (`favicon.ico` / `favicon-32.png` / `apple-touch-icon.png`,
  ~11 KB total) are duplicated into `openviking/server/static/` and shipped
  via package-data so `/favicon.*` and `/mcp/favicon.*` routes are always
  registered, regardless of whether the web-studio dist is bundled.
- `pyproject.toml` and `setup.py` `package-data` drop `console/static/**`
  and add `server/static/**` + `web_studio/dist/**`.
- New favicons (the 16/32/180 set in both `openviking/server/static/` and
  `web-studio/public/`) are downscaled from the canonical
  `web-studio/public/openviking-icon.png`, so the small-icon family matches
  the SPA's high-res rel="icon" target — the studio tab icon now stays
  consistent whether the browser uses the HTML link tag or falls back to
  auto-fetching `/favicon.ico`.

Server:
- `openviking/server/app.py` now reads `/studio` from
  `Path(__file__).parent.parent / 'web_studio' / 'dist'` by default;
  `OPENVIKING_WEB_STUDIO_DIR` still wins for dev mode pointing at a
  repo-local build. Favicon routes are unconditionally registered and
  load from `openviking/server/static/`.
- `openviking/observability/usage_audit/projection.py` drops the legacy
  `/console/*` skip prefix (the BFF prefix `/api/v1/console/*` remains).

Docker:
- `web-studio-builder` stage moved earlier (Stage 2) so its dist can flow
  into `py-builder` before `uv sync` runs.
- Runtime stage no longer separately copies the dist or sets
  `OPENVIKING_WEB_STUDIO_DIR`; the in-package path is the default.
- Entrypoint renamed `openviking-console-entrypoint.sh` -> `openviking-entrypoint.sh`
  and stripped of the `python -m openviking.console.bootstrap` launch.
- `EXPOSE 1933 8020` -> `EXPOSE 1933`.
- `docker-compose.yml` drops the openviking service's 8020 port mapping;
  the caddy service stays but no longer needs port 8020 exposed.
- `Caddyfile` simplified to a single `:1934 { reverse_proxy openviking:1933 }`
  — the legacy `/console/*` route to :8020 is gone.

Docs:
- en/zh quickstart updated to drop the 8020 mapping and explain that the
  API server now also serves `/studio`.
- Other guides (`12-public-access.md`, `11-oauth.md`, `05-observability.md`,
  `04-setup-for-agent.md`, `03-deployment.md`) are intentionally left for a
  focused follow-up PR alongside the OAuth quick-authorize reintroduction.

Tests:
- Deleted `tests/misc/test_console_{proxy,static_assets}.py` (covered the
  removed console package). `tests/observability/test_console_router.py`
  stays — it covers the BFF, which remains.
qin-ctx pushed a commit that referenced this pull request May 21, 2026
…o in pip, fix favicons (#2160)

The OpenViking docker image still launched the legacy `openviking/console`
standalone service on port 8020. Now that web-studio is bundled into the OV
server itself at /studio (see #2156), that process is redundant and the
port is just a confusing artefact.

This change retires the old console (python package + 8020 + console-frontend
favicons) but **keeps the in-compose Caddy as a stable single-ingress on
port 1934**, just simplified to one upstream now that there's no 8020. The
server-side BFF at `openviking/server/routers/console.py` (under
`/api/v1/console/*`) is also kept — web-studio uses the same endpoints.

**The OAuth authorize page (`openviking/server/oauth/router.py`) is
deliberately untouched in this PR** — the console-link button and Quick
authorize same-origin panel will be re-pointed at web-studio in a focused
follow-up.

BREAKING CHANGES:
- Port 8020 is gone from the docker image and docker-compose.yml; Caddy at
  1934 now forwards everything to 1933 (web-studio lives at /studio there).
  Anything bookmarked at `http://host:8020/...` must migrate to
  `http://host:1933/studio/`.
- `python -m openviking.console.bootstrap` no longer exists; the python
  package `openviking.console` has been removed.

Pip packaging:
- web-studio dist is now shipped inside the wheel under
  `openviking/web_studio/dist/` (mirroring the old `openviking/console/static/`
  layout). The dockerfile copies `--from=web-studio-builder /web-studio/dist`
  into the source tree before `uv sync`, so the wheel produced by the
  default docker build always carries the SPA. Building the wheel without
  running `npm run build` first leaves the directory empty, which gracefully
  degrades /studio to a 404 without breaking server startup.
- Favicon assets (`favicon.ico` / `favicon-32.png` / `apple-touch-icon.png`,
  ~11 KB total) are duplicated into `openviking/server/static/` and shipped
  via package-data so `/favicon.*` and `/mcp/favicon.*` routes are always
  registered, regardless of whether the web-studio dist is bundled.
- `pyproject.toml` and `setup.py` `package-data` drop `console/static/**`
  and add `server/static/**` + `web_studio/dist/**`.
- New favicons (the 16/32/180 set in both `openviking/server/static/` and
  `web-studio/public/`) are downscaled from the canonical
  `web-studio/public/openviking-icon.png`, so the small-icon family matches
  the SPA's high-res rel="icon" target — the studio tab icon now stays
  consistent whether the browser uses the HTML link tag or falls back to
  auto-fetching `/favicon.ico`.

Server:
- `openviking/server/app.py` now reads `/studio` from
  `Path(__file__).parent.parent / 'web_studio' / 'dist'` by default;
  `OPENVIKING_WEB_STUDIO_DIR` still wins for dev mode pointing at a
  repo-local build. Favicon routes are unconditionally registered and
  load from `openviking/server/static/`.
- `openviking/observability/usage_audit/projection.py` drops the legacy
  `/console/*` skip prefix (the BFF prefix `/api/v1/console/*` remains).

Docker:
- `web-studio-builder` stage moved earlier (Stage 2) so its dist can flow
  into `py-builder` before `uv sync` runs.
- Runtime stage no longer separately copies the dist or sets
  `OPENVIKING_WEB_STUDIO_DIR`; the in-package path is the default.
- Entrypoint renamed `openviking-console-entrypoint.sh` -> `openviking-entrypoint.sh`
  and stripped of the `python -m openviking.console.bootstrap` launch.
- `EXPOSE 1933 8020` -> `EXPOSE 1933`.
- `docker-compose.yml` drops the openviking service's 8020 port mapping;
  the caddy service stays but no longer needs port 8020 exposed.
- `Caddyfile` simplified to a single `:1934 { reverse_proxy openviking:1933 }`
  — the legacy `/console/*` route to :8020 is gone.

Docs:
- en/zh quickstart updated to drop the 8020 mapping and explain that the
  API server now also serves `/studio`.
- Other guides (`12-public-access.md`, `11-oauth.md`, `05-observability.md`,
  `04-setup-for-agent.md`, `03-deployment.md`) are intentionally left for a
  focused follow-up PR alongside the OAuth quick-authorize reintroduction.

Tests:
- Deleted `tests/misc/test_console_{proxy,static_assets}.py` (covered the
  removed console package). `tests/observability/test_console_router.py`
  stays — it covers the BFF, which remains.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants