> ## Documentation Index
> Fetch the complete documentation index at: https://firebolt-aggregate-helm-docs-pr-4.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Web UI

> Run the optional web UI for a self-managed Firebolt engine, as a Kubernetes sidecar or as a standalone Docker container pointed at any engine.

A self-managed Firebolt engine can have a web UI for running queries from a browser. The UI is a single-page application served by nginx from the `ghcr.io/firebolt-db/firebolt-core-ui` Docker image. It listens on port `9100` and forwards the queries the browser submits to one engine node's [HTTP endpoint](./connecting-over-http). Query execution stays on the engine, so the machine running the UI needs HTTP access to that endpoint and nothing else: no object-storage access and no connectivity to the other nodes.

Run it in one of two ways:

* As a sidecar container in each engine pod, on Kubernetes with the Helm chart or the Firebolt Operator. Off by default.
* As a standalone container on any machine with Docker, pointed at an engine's HTTP endpoint. This is the way to get the UI for an engine running from the [standalone binaries](./standalone-binaries/overview).

## Run as a Kubernetes sidecar

### Helm chart

Set `uiSidecar` on the engine spec in your values file and upgrade the release:

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
engineSpec:
  uiSidecar: true
```

The chart adds the UI container to each engine pod and publishes port `9100` (named `web-ui`) on the engine Service.

### Firebolt Operator

Set `uiSidecar: true` on a `FireboltEngine`, or on a `FireboltEngineClass` to apply it to every engine that references the class:

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
apiVersion: compute.firebolt.io/v1alpha1
kind: FireboltEngine
metadata:
  name: my-engine
spec:
  uiSidecar: true
```

The operator injects its built-in `engine-web` container into each engine pod. The sidecar points at the engine in the same pod over loopback (`http://localhost:3473`). An engine value overrides the class value, which overrides the default (`false`). See the [`uiSidecar` field](./firebolt-operator/crd-reference/engine-crd-reference) in the CRD reference.

### Open the UI

The UI listens on port `9100` inside the engine pod. Forward that port from a pod to your workstation, then open `http://localhost:9100`:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
kubectl -n firebolt get pods
kubectl -n firebolt port-forward <engine-pod> 9100:9100
```

With the Helm chart, port `9100` is also published on the engine Service, so you can forward the Service instead:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
kubectl -n firebolt port-forward svc/my-engine-service 9100:9100
```

## Run the Docker image directly

The standalone container works against any engine endpoint, wherever the engine runs. `FIREBOLT_CORE_URL`, the URL the UI forwards queries to, is the only required setting.

On the machine that runs a `firebolt` node:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
docker run --rm --network=host \
  -e FIREBOLT_CORE_URL=http://localhost:3473 \
  ghcr.io/firebolt-db/firebolt-core-ui:latest
```

Then open `http://localhost:9100`. With `--network=host` the container shares the host's network namespace, so `localhost` is the host itself and port `9100` is served without a port mapping. The flag behaves this way on Linux, which is what engine hosts run.

The UI does not have to run next to the engine. Run it on any machine that can reach a node's HTTP port, including your workstation:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
docker run --rm -p 9100:9100 \
  -e FIREBOLT_CORE_URL=http://<node-address>:3473 \
  ghcr.io/firebolt-db/firebolt-core-ui:latest
```

Use a plain HTTP URL for `FIREBOLT_CORE_URL`. Point it at a `firebolt` node's HTTP endpoint directly (`http://<node>:3473`), not at a subpath: the UI forwards each query as a `POST /query`, and a node accepts SQL over `POST` on any path.

If you cannot expose the node directly and put a proxy in front, the proxy must satisfy what the UI sends, because it forwards requests unchanged:

* Serve the engine at the path `/query`. The UI always posts to `/query`, so a proxy that exposes the engine under any other prefix never reaches it.
* Accept plain HTTP without SNI or Host-based routing. The forwarded request keeps the browser's `Host` header (the UI's own origin), so an HTTPS proxy that routes by hostname does not match.
* Enforce access by network, not by credentials. The UI cannot attach an auth header or client certificate, so restrict the proxy with network controls instead.

### Configuration

| Setting             | Default  | Purpose                                                                     |
| :------------------ | :------- | :-------------------------------------------------------------------------- |
| `FIREBOLT_CORE_URL` | required | URL the UI forwards queries to. Must be reachable from inside the container |
| `APP_PORT`          | `9100`   | Port the UI listens on                                                      |

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
firebolt-core-ui image internals: nginx serves the static SPA and reverse-proxies
location /query to $FIREBOLT_CORE_URL with the URI preserved, so the upstream
receives POST /query. The engine's HTTP query handler matches POST on any path,
which is why a raw engine endpoint (http://node:3473) works unmodified. The proxy
forwards the browser's Host header and does not set proxy_ssl_server_name, so an
HTTPS upstream that requires SNI or Host-based routing fails the handshake or the
route; use plain HTTP, or an SSH tunnel when the endpoint is remote. There is no
mechanism to inject auth headers.

The container runs as non-root uid 1111 and writes only to /var/tmp/nginx, so the
image rootfs can be mounted read-only. Versioned tags (1.1.2, ...) and latest are
published on ghcr.io.

Container networking when FIREBOLT_CORE_URL targets the docker host itself: on
Linux use --network=host (shared network namespace, container localhost is the
host, port 9100 needs no mapping). --add-host=host.docker.internal:host-gateway
resolves to the bridge IP (typically 172.17.0.1) and therefore cannot reach
services bound to 127.0.0.1 on the host, such as a default ssh -L tunnel. On
Docker Desktop (macOS, Windows) it is the reverse: host.docker.internal reaches
loopback-bound host services, while --network=host is a no-op unless the opt-in
host networking feature (Docker Desktop 4.34+) is enabled.
```

## Security

The UI adds no authentication on top of the engine's HTTP endpoint, [which has none](./connecting-over-http#security). Anyone who can reach port `9100` can run queries with full access to the engine. Treat network reachability as the security boundary, exactly as for the engine endpoint itself: keep the port on loopback or a private network, and reach it from elsewhere through `kubectl port-forward` or an SSH tunnel.
