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
| Resource | File | Purpose |
|---|---|---|
aws_ecr_repository.nessie_viz | ecr.tf | ECR repo for the sidecar image |
aws_lb_target_group.nessie_viz | nessie_ui_alb.tf | ALB target group for port 19121 |
aws_lb_listener_rule.nessie_viz | nessie_ui_alb.tf | Path-based routing (/viz*) |
| SG ingress for 19121 | nessie_alb.tf | ALB → sidecar connectivity |
| CORS origin | nessie_s3.tf | https://nessie.app.autointel.ai in bucket CORS |
Environment variables (sidecar container)
| Variable | Source |
|---|---|
NESSIE_VIZ_HOSTED | "true" |
AUTH0_DOMAIN | var.auth0_domain |
AUTH0_VIZ_CLIENT_ID | var.auth0_viz_client_id |
AUTH0_API_AUDIENCE | var.auth0_api_audience |
Docker image
deployments/aws/docker/nessie-viz/Dockerfile — Python 3.13, copies nessie_viz.py +
viz/ assets. Built and pushed to ECR by the build_viz_image CI job (triggered by
changes to viz source files).
Auth0 setup (manual, one-time)
Create a Single Page Application in Auth0:
| Setting | Value |
|---|---|
| Allowed Callback URLs | https://nessie.app.autointel.ai/viz/ |
| Allowed Logout URLs | https://nessie.app.autointel.ai/viz/ |
| Allowed Web Origins | https://nessie.app.autointel.ai |
| Grant Types | Authorization 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
- Create Auth0 SPA application, note client ID.
- Set
AUTH0_VIZ_CLIENT_IDin GitHub Actions variables andauth0_viz_client_idin Terraform tfvars. - Merge PR (CI builds viz image, pushes to ECR with
prodtag). terraform apply(creates ECR repo, viz-reader role, sidecar container, ALB routing).- Verify at
https://nessie.app.autointel.ai/viz/.
Verification
- Visit
https://nessie.app.autointel.ai/viz/— should redirect to Auth0 login. - After login — table tree loads, run a SELECT query, data returns (vended creds).
- Local
nessie vizstill works unchanged (local mode is unaffected). - Load a table with a
readertoken and confirms3:PutObjectis denied (RBAC scoping).