Lakebase behind an app, an agent or a feature store
kept in this browser
Edit this pageAttaching a Lakebase database to a Databricks App as a resource, what the platform creates for the app's service principal, why the schema has to be the app's own, and the other platform features backed by Lakebase.
On this page10
What it is
A Databricks App — a Dash, Flask or Streamlit application running on the platform — gets a Lakebase database by adding it as a resource, the same way it gets a warehouse or a model endpoint. You pick the project, the branch and the database; the platform does the rest, and the application finds its connection details in environment variables.
The resource key is postgres. The older database key belongs to Provisioned.
Why it exists
An app that stores anything — a saved filter, an approval, a comment, a chat history — needs a database with an identity of its own, not a copy of the developer’s credentials. The resource wires that identity: the app’s service principal becomes a Postgres role, gets exactly the rights to connect and create, and the connection details arrive as environment variables rather than as secrets someone pastes.
How it works
Attaching it
In the app’s configuration, Add resource → Database, then project, branch and database. The one permission offered is Can connect and create — CAN_CONNECT_AND_CREATE in a bundle (see Declarative Automation Bundles and the Databricks CLI). Attaching needs CAN MANAGE on the project.
What the platform then does:
- creates a Postgres role named after the app’s service principal client ID;
- grants that role
CONNECTandCREATEon the database; - injects
PGHOST,PGPORT,PGDATABASE,PGUSER,PGSSLMODEandPGAPPNAMEinto the app’s environment.
A standard Postgres client picks those up with no configuration:
import os, psycopg
with psycopg.connect(
host=os.environ["PGHOST"],
dbname=os.environ["PGDATABASE"],
user=os.environ["PGUSER"],
password=token(), # an OAuth token for the app's service principal, refreshed hourly
sslmode=os.environ["PGSSLMODE"],
) as conn:
conn.execute("CREATE SCHEMA IF NOT EXISTS shop_app_schema")
The password is still an OAuth token that expires after an hour; the app fetches one per connection (see Connecting to Lakebase, roles and permissions).
The schema has to belong to the app
CONNECT and CREATE mean the service principal can create objects, not that it can use objects somebody else owns. The app’s schema must therefore be created by the app, which makes the service principal its owner. The templates follow the convention {app-name}_schema_{sp-id}.
This is where most first days with Lakebase go wrong. If the application is run locally first, against the same database, the schema is created by your identity; when the app is then deployed, its service principal gets permission denied on a schema it does not own. The fix is the same either way — the schema has to end up owned by the service principal — but it means either dropping the schema, which destroys the data in it, or re-granting ownership. Deploying once before running locally avoids the whole situation.
The other half of that arrangement: to work on the data locally, ask for access to the objects the service principal owns (a member of databricks_superuser, or explicit grants), rather than creating your own copies.
Removing the resource reassigns the objects to whoever removes it, if they have CAN MANAGE.
Reading lakehouse data from an app
An app usually needs both: its own tables, which it writes, and lakehouse data, which it reads. The second comes from a synced table in the same database. After the sync is running and the app is deployed, its role needs read access to it:
GRANT USAGE ON SCHEMA gold TO "<service-principal-client-id>";
GRANT SELECT ON ALL TABLES IN SCHEMA gold TO "<service-principal-client-id>";
ALTER DEFAULT PRIVILEGES IN SCHEMA gold GRANT SELECT ON TABLES TO "<service-principal-client-id>";
Keeping synced tables in their own schema, separate from the app’s, is what makes that grant precise.
The same database, one branch per environment
Because a branch is a full copy with its own connection details, a staging app and a production app can point at two branches of one project. A branch also makes a demo safe: give the demo app a branch with a TTL, and it disappears with the demo.
Elsewhere in the platform
Lakebase is the storage under features you may already use without having created a project:
- the Online Feature Store, where
fe.create_online_storeprovisions a Lakebase project and serving endpoints read features by key; - the persistent chat history of the agent app templates, and the state an agent keeps between turns (see Deploy an agent on Databricks Apps and Agent memory).
Example
A review app for a data team: reviewers see rows that need a decision, and their decisions are stored.
- A project, one branch per environment, and a database.
- A synced table brings
main.gold.pending_reviewsinto thegoldschema, continuously. - The app is attached with the
postgresresource and deployed first, so its service principal creates and ownsreview_app_schema, where the decisions table lives. - The service principal is granted
SELECTongold.
The app reads current data it does not own, writes decisions it does, and holds no credentials: its identity is the service principal, and its permissions are two grants you can read out loud.
Common mistakes
- Running locally before deploying. The schema ends up owned by a person, and the deployed app cannot use it.
- Changing the resource key from
databasetopostgreson an existing app. That creates a new role and loses access to what the old one owns. - Expecting the app to read every table. It can create and use its own objects; anything else needs a grant.
- One database for everything. Branches per environment cost almost nothing and keep a staging bug away from production rows.
- Holding a token for the life of the process. It expires after an hour; fetch one per connection.