Skip to main content

Hosting the Iceberg Visualizer

The visualizer runs locally via nessie viz with no hosting — see the design doc. The hosted variant adds browser-only access for users without the repo/uv toolchain.

Architecture

The hosted visualizer runs as a sidecar container in the existing Nessie ECS task. Same ALB, same domain (nessie.app.autointel.ai), path-based routing splits traffic:

ALB (nessie.app.autointel.ai)
├── /viz* → nessie-viz target group (sidecar:19121)
└── /* (default) → nessie target group (nessie:19120)

The sidecar proxies catalog requests to localhost:19120 (Nessie on the same task), keeping all catalog traffic same-origin — no Nessie CORS needed.

Auth

Auth0 PKCE (public SPA client). The browser authenticates directly via auth0-spa-js and attaches Authorization: Bearer <token> to every fetch. The sidecar forwards the header to Nessie as-is — Nessie validates it via its existing service OIDC tenant.

S3 credentials

Nessie vends short-lived, per-table-scoped S3 credentials in the loadTable REST response. DuckDB requests them via ACCESS_DELEGATION_MODE 'vended_credentials' at ATTACH time. A reader token gets read-only creds; service/admin get read-write. No separate AWS secret or STS endpoint needed.

Infrastructure

Terraform resources

ResourceFilePurpose
aws_ecr_repository.nessie_vizecr.tfECR repo for the sidecar image
aws_lb_target_group.nessie_viznessie_ui_alb.tfALB target group for port 19121
aws_lb_listener_rule.nessie_viznessie_ui_alb.tfPath-based routing (/viz*)
SG ingress for 19121nessie_alb.tfALB → sidecar connectivity
CORS originnessie_s3.tfhttps://nessie.app.autointel.ai in bucket CORS

Environment variables (sidecar container)

VariableSource
NESSIE_VIZ_HOSTED"true"
AUTH0_DOMAINvar.auth0_domain
AUTH0_VIZ_CLIENT_IDvar.auth0_viz_client_id
AUTH0_API_AUDIENCEvar.auth0_api_audience

The service

services/ai_nessie_viz/ — a shell around packages/ai_nessie, which owns the server and the SPA. Only the deployment specifics live in the service: bind-all, the in-task Nessie address, and the AUTH0_* names the task definition supplies. It is built like every other image in the repo — pants package services/ai_nessie_viz/src:pex, then a venv materialized from the pex — by the generic build_service_image CI job, which discovers it from services/*/pyproject.toml.

Its build.yaml declares registry: (the nessie-viz ECR repo, which does not match the service name) and sidecar: true, so CI pushes the image without rolling anything. This is a sidecar in the nessie service, and rolling it would restart the Nessie catalog. A new image is collected on the next deployment of that task.

ai_core must stay out of the image. ai_nessie depends on it for the CLI's device-flow auth, but nothing reachable from main() imports it, so Pants leaves ai_core (and pyiceberg, boto3, polars) out of the pex. services/ai_nessie_viz/tests/test_image_surface.py fails if that stops holding.

Auth0 setup (manual, one-time)

Create a Single Page Application in Auth0:

SettingValue
Allowed Callback URLshttps://nessie.app.autointel.ai/viz/
Allowed Logout URLshttps://nessie.app.autointel.ai/viz/
Allowed Web Originshttps://nessie.app.autointel.ai
Grant TypesAuthorization Code (PKCE)

Note the Client ID and set it as:

  • GitHub Actions variable: AUTH0_VIZ_CLIENT_ID
  • Terraform variable: auth0_viz_client_id

The existing Login post-login Action already injects roles into access tokens — the viz SPA gets the same RBAC claim as the CLI device flow.

Deploy order

  1. Create Auth0 SPA application, note client ID.
  2. Set AUTH0_VIZ_CLIENT_ID in GitHub Actions variables and auth0_viz_client_id in Terraform tfvars.
  3. Merge PR (CI builds viz image, pushes to ECR with prod tag).
  4. terraform apply (creates ECR repo, viz-reader role, sidecar container, ALB routing).
  5. Verify at https://nessie.app.autointel.ai/viz/.

Verification

  1. Visit https://nessie.app.autointel.ai/viz/ — should redirect to Auth0 login.
  2. After login — table tree loads, run a SELECT query, data returns (vended creds).
  3. Local nessie viz still works unchanged (local mode is unaffected).
  4. Load a table with a reader token and confirm s3:PutObject is denied (RBAC scoping).