By Team
ScriptlyStore Core Engine
Relational database schema changes have historically been the most nerve-wracking part of deploying web applications. If a migration goes wrong, it can lock tables, corrupt production data, or crash your service.
With Neon Postgres, the concept of Database Branching eliminates this risk. Just like git branching, Neon lets you create copy-on-write database branches in seconds, allowing you to run migrations on isolated copies of production data before pushing changes live.
This guide outlines how to configure Drizzle migrations, automate preview branch creation in GitHub Actions, and achieve zero-downtime schema deployments.
| Deployment Phase | Traditional Database | Neon Postgres Branching | Risk Level |
|---|---|---|---|
| Local Development | Shared staging or local SQLite | Isolated dev branch cloned from prod | Low |
| Pull Request Review | None (untested db state) | Ephemeral PR branch with actual schemas | Low |
| Migration Testing | Staging migrations, manual checks | Run migrations against copy of prod | Low |
| Production Rollout | Run migration directly on live DB | Apply validated SQL schema to Main branch | Medium |
First, ensure Drizzle Kit is configured to generate and output migrations into your project.
Create a drizzle.config.ts file:
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/db/schema.ts",
out: "./src/db/migrations",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});Generate your migration files:
npx drizzle-kit generateThis command outputs SQL migration files under src/db/migrations/.
Using the Neon CLI, you can create a temporary branch for testing migrations in your CI pipeline.
npm install -g @neondatabase/api-client neon branches create --project-id <project-id> --name pr-preview-db --parent mainThis creates a new connection string representing an isolated database containing a copy of your main schema and data.
By combining Neon database branching with GitHub Actions, you can run migrations automatically on every pull request. If the migrations run successfully, the PR passes. If they fail, the PR fails, protecting your production database.
name: Test Database Migrations
on:
pull_request:
branches: [ main ]
jobs:
test-migrations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Create Neon Database Branch
id: create-branch
run: |
# Use Neon API to create branch and get connection string
CONNECTION_STRING=$(curl -X POST "https://console.neon.tech/api/v2/projects/${{ secrets.NEON_PROJECT_ID }}/branches" \
-H "Authorization: Bearer ${{ secrets.NEON_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"branch": {"name": "pr-${{ github.event.number }}-db", "parent_id": "main"}}' \
| jq -r '.connection_uri')
echo "DATABASE_URL=$CONNECTION_STRING" >> $GITHUB_ENV
- name: Run Schema Migrations
run: npx drizzle-kit migrate
- name: Delete Neon Branch
if: always()
run: |
curl -X DELETE "https://console.neon.tech/api/v2/projects/${{ secrets.NEON_PROJECT_ID }}/branches/pr-${{ github.event.number }}-db" \
-H "Authorization: Bearer ${{ secrets.NEON_API_KEY }}"Even with branching, follow these rules to ensure your app stays online during schema changes:
CONCURRENTLY keyword to prevent blocking reads and writes.