WritingDatabricks (DBRX)Databricks (DBRX)published Jul 20, 2026seen 6d

Branching databases like code: a CI/CD pattern for Lakebase, in production at Glaspoort

Open original ↗

Captured source

source ↗

Branching databases like code: a CI/CD pattern for Lakebase, in production at Glaspoort | Databricks Blog Skip to main content

Summary

How Glaspoort designed its Lakebase branching setup, with development and acceptance both branched directly from production instead of stacked on top of each other, to avoid the "reset-from-parent trap" where refreshing a stale environment forces you to delete and rebuild everything underneath it.

The per-PR CI/CD flow: how every pull request gets its own fresh, disposable Lakebase branch copied from production, how migrations get replayed and tested against a live app image before anything touches a real environment, and why the migrations themselves (not the databases) are treated as the source of truth.

The tradeoff between two promotion models: merging as soon as CI passes versus merging only after a PR is fully promoted through acceptance. The post explains why Glaspoort picked the velocity first approach, and the safeguards they added (stack revalidation and a crisis pipeline) to keep it safe.

The problem we couldn't ignore Glaspoort builds and operates fiber infrastructure in the Netherlands. Everything revolves around growing the number of fiber connections, and for a long time the data team spent its days building BI reports in support of that goal, while the question behind each report was already outdated by the time the report was finished. The result was a sprawl of one-off reports, and users who had nowhere to take their follow-up questions. So we broke the dependency. Instead of shipping the next report, we built a custom front-end application in which project managers see directly where the opportunities for their projects lie. What is new sits under the hood: we use Databricks products directly as building blocks in the app. Genie, to chat with the data and spin up quick analyses. AI/BI Dashboards, for insights and self-service analytics. Automated workflows with Agent Bricks, which alert project managers the moment something stands out on their projects and Lakebase, the Databricks OLTP database , for the application's transactional data. That combination brings together two worlds that until recently lived apart: the analytical environment and an operational front-end, where analytical data meets transactional data. The data team now spends its time on Genie spaces and metadata instead of one-off reports. But none of this stays fast without a serious foundation underneath: CI/CD, data-quality testing, Infrastructure as Code, and data governance. One piece of that foundation took the most careful design, and it is the piece the rest of this story is about: how we ship changes to the database behind the app. Behind that application sits a Databricks Lakebase database . It is serverless Postgres OLTP, running next to the lakehouse rather than bolted onto it. The data flow is simple to describe and, as we found out, more interesting to operate than it looks: Curated data from the lakehouse is synced into a Lakebase production branch, where it lands in a read-only application schema. The application writes its own state back into a separate schema on that same branch, so the data read from the lakehouse and the data written by the app live side by side without colliding. On top of that we run three logical environments: development, acceptance, and production. We ship changes to this database the same way we ship changes to application code, through pull requests, CI, and gated promotion.

That last point is where the interesting question lives. The moment you decide an OLTP database deserves the same rigor as application code, you have to answer one hard question: How do we let every PR test against a database that looks like production, without people stepping on each other, and without losing the fresh data that makes the test meaningful in the first place? The failure modes here are familiar to anyone who has shared a database across a team. PRs that pass in isolation and break when they land together. Dev and acceptance environments that have quietly drifted away from what production actually looks like. And the refresh day nobody wants, where getting clean data back means tearing environments down, re-wiring every connection string, and re-applying every grant by hand. This post is about how we avoided most of that, and the one decision we are still actively debating. Lakebase branching, in 60 seconds If you have not used Lakebase branches yet, here is the only mental model you need for this post. A Lakebase branch is a copy-on-write Postgres branch off a parent . Creating one does not copy the data; it forks the state cheaply and instantly, and the branch only diverges from its parent as you write to it. Each branch is fully isolated, with its own endpoint and its own data. You can create one in seconds and throw it away just as fast. If that sounds like git, that is the point. The analogy that organizes everything below is simple: a feature branch in git maps to a database branch in Lakebase. A PR gets its own code branch and its own database branch, tests run against both, and when the work is good it gets promoted toward production. There is one constraint to carry into the next section, because it is a critical distinction from git branching. To reset a branch from its parent, you first have to delete that branch's own children. A parent cannot be reset out from under the branches that depend on it. The reset-from-parent trap Here is the design most teams reach for first, because it mirrors the way we draw environments on a whiteboard: A single long-lived development branch off production. An acceptance branch off development. Feature branches off development.

It is a clean hierarchy: production at the root, then dev, then acceptance and features hanging beneath it. It also introduces two predictable failure modes: the branches drift, and the fix for drift is expensive enough that teams stop applying it. First, dev and acceptance drift from production. Production keeps receiving fresh synced data and real application writes; the long-lived dev and acceptance branches do not. Within a sprint or two you are testing against a database that no longer resembles the one you are shipping to. The obvious fix is to refresh dev from production, and this is where the constraint from the last section turns into a tax. To reset development from its parent, you must first delete development's children, which...

Excerpt shown — open the source for the full document.