The database console
When you run a database on Flui — a Postgres or MariaDB picked from the catalog, a Redis or Valkey cache bundled with an app — you eventually need to look inside it: run a query, check a row, see what keys are taking up space. Historically that meant either exposing the database to the internet (a bad idea), bolting on a separate web tool like pgweb (one more thing to host and secure), or reaching for a native client and a hand-rolled tunnel.
The database console removes that gap. It is a first-party SQL and key-value runner built into the dashboard and served through Flui’s own backend — no extra tool to install, and no public database endpoint anywhere. This chapter is the conceptual picture: which engines it speaks, how it connects, what it deliberately will not do, and where the natural-language helper draws its safety line.
What it covers today
The console is organised around two engine families, each with its own surface:
- SQL — Postgres and MariaDB. A query editor that runs SQL, a schema browser that introspects tables, columns, primary keys and foreign keys, and a connection-info panel.
- Key-value — Redis and Valkey. A keyspace overview (counts, not contents), a key scan, a value reader, and a command runner.
These map directly to the building blocks Flui already ships in its catalog. The engine is not something you configure — Flui detects it from the database’s container image, and falls back to detecting a SQL engine from the user/database environment variables it has set. Adding a new engine is a matter of one engine profile plus one adapter on Flui’s side; the query, transport and helper layers stay engine-agnostic.
Read-only by default, and it means it
The single most important property of the SQL console is that every
query runs read-only by default. This is not a UI checkbox that
filters out the word DELETE — it is enforced at the database
session itself. For Postgres, each query opens a transaction, sets
it READ ONLY, applies the limits, and then rolls back rather
than commits. A statement that tries to write fails at the engine;
nothing it did persists, because the transaction is discarded
regardless.
Two more guards ride along on every query:
- A statement timeout of about 30 seconds. A query that runs
away — a missing
WHERE, an accidental cross join — is cut off by the database rather than holding a connection open indefinitely. - A row cap of about 1000 rows. The grid shows the first slice and tells you the result was truncated. The console is for inspection, not for bulk export.
The key-value side follows the same shape. Browsing — the keyspace
summary, the key scan, reading a value — is inherently read-only.
The command runner passes through a read-only gate: arbitrary
commands are allowed only when they are reads; a write or
destructive command (a FLUSHALL, say) is rejected unless the
write path is explicitly enabled.
The reason for all of this is blast radius. The console is the everyday “let me just check something” tool, and the everyday tool should be incapable of breaking the database it is inspecting. The write path exists in the design, but it is gated, audited, and not the default.
How it connects: the owner-credential model
To run a query, the console has to authenticate to the database. Today it does that with the building block’s own owner (superuser) credentials — the same username, password and database that the catalog generated when it installed the building block.
Those credentials are not stored a second time and are not typed in
by you. Flui reads them, at the moment of the query, straight from
the Kubernetes Secret that the building block already owns (the
<slug>-secret the install generated). SQL engines carry a user
and a database; key-value caches authenticate with the password
alone, and are often deployed with no auth at all, in which case the
console connects anonymously. The engine-specific details — which
Secret key holds the password, which port to use, which environment
variables name the user and database — come from a per-engine
profile, so the connection logic itself never branches on the
engine name.
This is the honest description of the current state: everyone who can open the console connects as the database owner. That is acceptable today because the console’s default is read-only and every action is audited server-side — but it is a known limitation, not a destination.
The forward path: per-user roles
The piece of the design that makes the owner-credential model
temporary is a deliberate seam. The thing that turns “this app” into
“a live connection” is a single, narrow interface — a connection
resolver. The implementation in place resolves the building
block’s owner Secret and reports the role as owner. A future
implementation will provision or resolve a dedicated, per-Flui-user
role on the database and report the role as user — and it will
slot in behind the exact same interface, with no change to the query
layer, the transport, or the helper above it. That is the path from
“everyone is the owner” to “each person connects as themselves, with
their own grants.”
How the dashboard reaches the database
Flui databases are not exposed outside the cluster network. There is no public database endpoint to firewall, rotate, or worry about. So how does a console running in your browser reach a database sitting on a private cluster?
It goes through Flui’s backend. For each query, the backend opens a short-lived in-cluster tunnel to the database pod, runs the query over it, and tears the tunnel down again when the call returns. The browser never talks to the database directly; it talks to the Flui API, which is the only thing that ever holds a connection. Nothing about the database’s address or password is handed to the browser.
That same property is why the connection-info panel is safe to show. It returns the coordinates you need to reason about a connection — engine, database name, user, in-cluster port — and never the password. The password does not travel over the HTTP API at all.
Connecting a native client from your machine
The in-browser console is for inspection. When you want the real
thing — psql, the mariadb client, DBeaver, or an ORM pointed at
the database from your laptop — the CLI gives you two commands that
honour the same “no public endpoint” rule. They are covered in full
on the databases CLI page; the shape is:
flui db tunnel <app>opens a local tunnel to the database. It forwards through the control plane over SSH and an in-cluster port-forward, bound to127.0.0.1on your machine — so the database becomes reachable aslocalhost:<port>for the life of the command, and nothing is opened to the outside world. It then prints a ready-to-paste connection string and a client command.flui db credentials <app>prints the in-cluster address and credentials for connecting another app running on Flui to the database, plus aDATABASE_URL. The password is read from the in-cluster Secret over SSH and is hidden unless you pass--show.
In both cases the password is fetched straight from the cluster Secret over SSH — never from the API — which is the same discipline the in-browser console follows.
The AI helper is data-blind
Both consoles offer a natural-language helper: describe what you want in plain English, and it returns a SQL query (or a Redis command) plus an explanation, which you then review and run yourself. The helper never executes anything on your behalf — and, more importantly, it never sees your data.
This is the load-bearing safety property, so it is worth being precise. When you ask the SQL helper a question, what gets sent to the language model is exactly three things:
- The schema — table and column names, types, primary keys and foreign keys. Structure only.
- Your question — the natural-language prompt.
- A curated, per-engine knowledge base — Flui’s own baked-in notes on the engine’s dialect, query patterns and gotchas (a distinct corpus for Postgres, for MariaDB, and for Redis/Valkey), so the suggestions fit the engine you are actually running.
What is never sent is any row data. The helper introspects the schema, but it does not read, sample, or forward the contents of a single row. The key-value helper is even more conservative: it sees only the keyspace summary — a key count and a per-type breakdown — never key names or values. This “data-blind by contract” design is what makes it safe to use a hosted model against a production database: the model can reason about shape without ever being exposed to the contents.
The query the helper writes still runs under all the same rules as a hand-typed one — read-only by default, the statement timeout, the row cap. A suggestion that would change data is flagged as a mutation so the dashboard can put a confirmation in front of it, but the read-only transaction underneath is the real boundary; the flag is only a hint.
The helper does not ship its own model. It uses the same inference layer the rest of Flui’s assistant features use, which means it inherits whatever you have configured there — Flui’s native inference or your own bring-your-own provider. That story has its own chapter: Inference and bring-your-own LLM.
What it is, and what it is not
To set expectations cleanly:
- It is a fast, safe way to inspect the databases you already run on Flui — query, browse schema, scan keys, get a query draft — without exposing them and without a second tool to host.
- It is not a full database administration suite, a migration runner, or a bulk-export tool. The row cap and the read-only default are deliberate; they are what make it the right tool to reach for first.
Where to read next
- The app concept — databases are apps in Flui’s model, with the same lifecycle, logs and metrics as anything else you run.
- Catalog vs application — where the Postgres, MariaDB, Redis and Valkey building blocks the console operates on come from.
- Inference and bring-your-own LLM — the inference layer the data-blind helper draws on.
- The databases CLI —
flui db tunnelandflui db credentialsfor connecting native clients.