Database Branching Workflows
Boost development velocity by adding data to your existing GitHub workflows
Git revolutionized how we develop software, but the story is quite different when databases are involved. Rapid development cycles that include databases remain elusive as they continue to stubbornly resist the agile tooling that has become standard in other parts of our tech stack.
Bringing Branching to Databases
To change this, Neon proposes adopting database branching that includes both data and schema. Database branches enable instantaneous access to full database copies for developers, who can then modify those copies without impacting the production database — effectively extending the Git concept of code branching to your data.
Branching data and schema
The integration of databases within development practices isn’t a new idea. Modern teams already use tools to manage schema changes, and an increasing number of database providers are embracing schema branching. But for development workflows involving databases to be effectively unblocked, developers require the ability to not only modify schemas but also work with and modify isolated copies of data across different environments, similar to how code can be safely modified in Git branches.
Git enables collaboration and rapid development by maintaining a detailed history of commits. Neon mirrors this concept via a custom-built, log-structured storage system, which treats the database as a record of transactions. Neon captures a comprehensive history of data snapshots, ensuring that developers can manage and revert changes in data states with the same ease as they do with code versions.
Adopting Branch-based Deployments
Traditionally, database deployments are instance-based, with each environment represented as a separate, self-contained instance. By shifting our perspective to view these as branch-based deployments, we can align more closely with familiar Git workflows. In this model, each environment has a database branch, which functions similarly to a code branch. For example:
- Production database branch: The main branch where the live data resides.
- Preview database branches: Temporary branches for reviewing new features or updates.
- Testing branches: Temporary branches dedicated to automated testing.
- Development branches: Where developers experiment and iterate on new features.
Database Branching Workflows
To illustrate how a branch-based deployment model can be achieved in practice, we’ll cover two key workflows:
-
Preview Environment Workflow (one database branch per PR)
- In this workflow, we automatically create a preview environment for every pull request with its own Neon database branch. We use Fly.io as the deployment platform and Drizzle as the ORM.
- The preview database branch gives the developer access to an isolated “copy” of the database. Any code modifications will be tested against this database.
- Once the PR is merged, the preview environment and its database branch will be automatically deleted.
-
Local Development Environment Workflow (one database branch per developer)
- This workflow showcases how to use database branching to create personalized development environments for every developer on a team.
- Each developer gets instant access to an isolated “copy” of production-like data.
- When the work is done, a developer can reset their development environment to the current state of
main
(both data and schema).
Preview Environment Workflow
One database branch per pull request
The code for this workflow is provided in this repo, and also described in the following video:
The Tech Stack
How it Works
This is a GitHub Actions workflow that automatically deploys preview applications associated with PRs using Fly.io. Each preview environment is deployed with its own Neon database branch:
Let’s break down the steps in this preview environment workflow where we have one database branch for each pull request:
- Checkout code: Checks out the code of the PR to the runner.
- Setup Node.js: Sets up a Node.js environment, using version 4 of the actions/setup-node action.
- Install dependencies: Runs
pnpm install
to install necessary Node.js packages. - Get the git branch name: Uses the
tj-actions/branch-names@v8
action to get the name of the current branch. - Set up the Neon database branch: Uses the
neondatabase/create-branch-action@v5
action to set up a preview database branch and exports aDATABASE_URL
environment variable for further use. - Perform schema migrations: Executes
pnpm run db:migrate
to apply database migrations using theDATABASE_URL
from the previous step. - Deploy: Uses the
superfly/fly-pr-review-apps@1.2.0
action to deploy the application to Fly.io. This action takes theDATABASE_URL
as input undersecrets
, ensuring the app connects to the correct Neon database branch.
To automatically delete a preview environment when a PR is closed, including the associated Neon database branch, we use this Clean up Preview Deployment GitHub Actions workflow:
The superfly/fly-pr-review-apps@1.2.0
action deletes the preview environment associated with the PR, while the neondatabase/delete-branch-action@v3.1.3
action deletes the associated database branch.
Local Development Environment Workflow
One database branch per developer
The Tech Stack
Workflow Steps
These are the basic steps to implement a local development workflow with a database branch per developer:
-
Connect your Neon account:
-
Create a database branch for each developer:
-
Get the connection string for the Neon database branch:
-
Optionally, reset the development branch to the current state of
main
(data and schema).This step is useful when you want to discard the changes in your existing dev branch and start fresh with dev branch that reflects the current state of the parent's data and schema.
Upcoming Improvements
The workflows described above are basic examples. We’ll keep expanding on them to provide complete production deployments with staging environments and handling of Personal Identifiable Information (PII). This work is ongoing.