Databricks Lakebase#
pgmi deploys to Databricks Lakebase like any PostgreSQL server: one direct session to the database’s endpoint. This page covers what differs: the credential, running pgmi from a bundle job, the advanced template’s provider settings, and a test gate on a Lakebase branch.
What was checked, and when. Lakebase projects (autoscaling, the
databricks postgresAPI) on Azure Databricks, westeurope, PostgreSQL 18.6, Databricks CLI v1.24.0, between 2026-11-06 and 2026-11-09:
- a serverless bundle job deploying the basic and the advanced template with pgmi v0.13.1, and the basic template with v0.14.0;
- the advanced template with the provider settings, from a workstation, on a v0.14.1 pre-release build;
- the branch test gate, from a workstation, with v0.14.0.
The job script below is condensed from the tested one. Lakebase changes quickly: check the Databricks Postgres documentation for your region before relying on a detail.
Connect#
| Setting | Value |
|---|---|
| Host | the endpoint’s direct host (databricks postgres get-endpoint <endpoint>, status.hosts.host) |
| Port, SSL | 5432, sslmode=require |
| User | a Databricks identity with a Postgres role: the user name, or a service principal’s application ID |
| Password | a database credential for that identity, valid for one hour |
| Maintenance database | databricks_postgres |
set -euo pipefail
token=$(databricks postgres generate-database-credential \
projects/<project>/branches/production/endpoints/primary -o json | jq -er .token)
export PGPASSWORD=$token
pgmi deploy . --connection "host=<endpoint host> port=5432 dbname=databricks_postgres user=<you> sslmode=require" \
-d myapp --param env=devDo not run this under set -x: the trace prints the token. Get the credential
right before the deploy. Lakebase checks its expiry only at login, so a deploy
that is already connected may run past the hour.
A plain Microsoft Entra access token is refused as the password, so pgmi’s
--azure does not work against Lakebase; get the credential first, as above.
Only the identity that created the project has a Postgres role from the start.
Another user or service principal needs one before it can connect: after
CREATE EXTENSION IF NOT EXISTS databricks_auth, run
SELECT databricks_create_role('<name>', 'USER') ('SERVICE_PRINCIPAL' with
its application ID), then grant it what it needs.
The creator can CREATE DATABASE, so -d may name a new database. It also has
BYPASSRLS and belongs to databricks_superuser: row-level security does not
filter what it reads, even on a forced table. Never let an application do a
user’s work as the deploy identity.
Deploy from a bundle job#
A serverless job task can download pgmi, get a credential for the identity it
runs as, and deploy. No credential configuration is needed inside the job: the
Databricks SDK authenticates as the job’s identity, which is the identity that
deployed the bundle unless the bundle sets run_as. When that identity created
the Lakebase project, it has the rights the deploy needs.
# resources/db_deploy.yml
resources:
jobs:
db_deploy:
name: myapp-db-deploy
max_concurrent_runs: 1
tasks:
- task_key: pgmi_deploy
environment_key: default
spark_python_task:
python_file: ../jobs/deploy_db.py
parameters:
- --db-dir=${workspace.file_path}/db
- --endpoint=projects/myapp/branches/production/endpoints/primary
- --database=myapp
- --param=env=prod
environments:
- environment_key: default
spec:
environment_version: "5"
dependencies:
- databricks-sdk>=0.81.0# jobs/deploy_db.py
import argparse, hashlib, os, platform, subprocess, tarfile, tempfile, urllib.parse, urllib.request
from databricks.sdk import WorkspaceClient
VERSION = "0.14.1"
BASE = f"https://github.com/vvka-141/pgmi/releases/download/v{VERSION}"
ASSET = {"aarch64": f"pgmi_{VERSION}_linux_arm64.tar.gz",
"x86_64": f"pgmi_{VERSION}_linux_amd64.tar.gz"}[platform.machine()]
# From the release's checksums.txt, after `gh attestation verify <archive> -R vvka-141/pgmi`.
SHA256 = {"pgmi_0.14.1_linux_arm64.tar.gz": "<sha256>",
"pgmi_0.14.1_linux_amd64.tar.gz": "<sha256>"}
p = argparse.ArgumentParser()
for a in ("--db-dir", "--endpoint", "--database"):
p.add_argument(a, required=True)
p.add_argument("--param", action="append", default=[])
args = p.parse_args()
with tempfile.TemporaryDirectory() as tmp:
archive = urllib.request.urlopen(f"{BASE}/{ASSET}", timeout=60).read()
if hashlib.sha256(archive).hexdigest() != SHA256[ASSET]:
raise RuntimeError(f"{ASSET} does not match its pinned SHA-256")
path = os.path.join(tmp, ASSET)
with open(path, "wb") as f:
f.write(archive)
with tarfile.open(path) as tar:
tar.extractall(tmp, filter="data")
w = WorkspaceClient()
host = w.postgres.get_endpoint(name=args.endpoint).status.hosts.host
user = w.current_user.me().user_name
token = w.postgres.generate_database_credential(endpoint=args.endpoint).token
if not (host and user and token):
raise RuntimeError("no host, user or database credential for the endpoint")
q = urllib.parse.quote
conn = f"postgresql://{q(user, safe='')}:{q(token, safe='')}@{host}:5432/databricks_postgres?sslmode=require"
cmd = [os.path.join(tmp, "pgmi"), "deploy", args.db_dir, "-d", args.database, "--force"]
for param in args.param:
cmd += ["--param", param]
rc = subprocess.run(cmd, env={**os.environ, "PGMI_CONNECTION_STRING": conn}).returncode
if rc != 0:
raise RuntimeError(f"pgmi deploy failed with exit code {rc}")The SHA-256 values are pinned in the script on purpose. The archive and
checksums.txt come from the same release, so comparing one with the other
detects a corrupted download, not a replaced one.
What was observed on the serverless runtime:
- Both architectures. A serverless task ran on
aarch64orx86_64, and an automatic retry of the same run switched between them. Pick the release asset fromplatform.machine(). - A retry runs the deploy again. That is safe when the deploy converges, as the basic and advanced templates do. Otherwise turn off serverless auto-optimization for the job, which is what retries it.
- No
sys.exit(). In a serverlessspark_python_task(environment version 4), evensys.exit(0)marked the task failed. End normally on success and raise on failure. - Database driver.
pg8000(pure Python) worked when the job also queried PostgreSQL itself;psycopg[binary]aborted at import onaarch64(environment version 4, cause not found). - Outbound access to GitHub for the download worked in the tested account.
- The token only in the environment. pgmi reads it from
PGMI_CONNECTION_STRING; never pass it as a command-line argument or print it.
The advanced template in a job#
The advanced template needs more than env: the provider settings below, and
with env=test or env=prod an admin password. Pass the settings as
--param entries. Read the password from a Databricks secret and add it to
the environment of the pgmi call as PGMI_PARAM_DATABASE_ADMIN_PASSWORD,
never as a --param. This combination has not been run as a job yet.
The advanced template on Lakebase#
Observed, though not in the Databricks documentation: every database carries
event triggers that grant Lakebase’s own roles (databricks_reader_<id>,
databricks_writer_<id>, databricks_superuser, databricks_gateway) on every
new schema, table, view and sequence, and a schema __db_system. The advanced
template’s grant guards refuse that until you name them:
provider_role_pattern=databricks\_%
provider_schemas=["__db_system"]With both set, the deploy revokes the provider roles’ CREATE on the
template’s schemas and their TRUNCATE, REFERENCES, TRIGGER and MAINTAIN
on its tables, and the unmodified template deploys and redeploys with all its
tests. Grants a provider makes on its own
explains what the guards still check.
What this means for your own tables:
- Lakebase’s reader can
SELECTevery new table. The Databricks documentation shows the reader and writer roles withBypass RLS; on the tested projects they did not have it. Check yours:SELECT rolname, rolbypassrls FROM pg_roles WHERE rolname LIKE 'databricks\_%';. Where they do not bypass it, a table forced to row-level security with policies keeps them out; a role that bypasses it is never held back by a policy.databricks_superuser, the admin group, does bypass it. A table without row-level security is readable by the reader from the moment it exists. The template’stest_forced_rls.sqlrefuses one that holds an organization’s rows. - The admin login cannot sign in by password. New Lakebase projects refuse password logins unless the project allows them , which was not tested here. Sign in with your Databricks identity.
- Pass a strong admin password even with
env=dev. Lakebase checks passwords set in SQL for strength, and the template’senv=devdefault,postgres, was not tried there. The tested runs passed 48 random hex characters.
A test gate on a branch#
A Lakebase branch is a copy of another branch’s schema, data and roles, taken
when the branch is created. Deploy to a branch first, and to production only
when that deploy passes:
set -euo pipefail
P=projects/myapp
B=gate-$(date +%s)
databricks postgres create-branch $P $B --ttl 3600s
trap 'databricks postgres delete-branch $P/branches/$B' EXIT
deploy() { # deploy <branch>: a credential for that branch's endpoint, then pgmi
local ep=$P/branches/$1/endpoints/primary host token
host=$(databricks postgres get-endpoint $ep -o json | jq -er .status.hosts.host)
token=$(databricks postgres generate-database-credential $ep -o json | jq -er .token)
PGPASSWORD=$token pgmi deploy . -d myapp --param env=prod \
--connection "host=$host port=5432 dbname=databricks_postgres user=<you> sslmode=require"
}
deploy $B # the tests run on the copy; a failure stops the script here
deploy productioncreate-branch with only --ttl branched from production in the tested
runs; the --ttl deletes a branch the trap misses. Observed: the branch was
connectable 25 seconds after create-branch, had every role of production,
and a failing test left production unchanged. The cycle took about two
minutes.
A pass on the branch proves the deploy against that snapshot. production can
change before the second deploy, so keep the tests in the production deploy
too (the templates run them on every deploy), and let one pipeline at a time
deploy to production.