Skip to content

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 to 127.0.0.1 on your machine — so the database becomes reachable as localhost:<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 a DATABASE_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:

  1. The schema — table and column names, types, primary keys and foreign keys. Structure only.
  2. Your question — the natural-language prompt.
  3. 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.
  • 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 CLIflui db tunnel and flui db credentials for connecting native clients.