Automatically spin up an isolated, production-data database branch for every pull request. No shared staging conflicts. No stale data. Deleted on merge.
Most teams test database changes against a single shared staging environment. This works when there's one developer. It breaks down the moment multiple PRs are open simultaneously.
Two developers test simultaneously and overwrite each other's data. QA can't reproduce a bug because the data was changed by someone else.
Spinning up a separate test database takes 30+ minutes with pg_dump/restore. So teams skip it and test on shared staging instead — which leads to the problem above.
The staging database was last refreshed 3 weeks ago. Features that depend on recent production data fail in staging but work in prod — or the reverse.
Each step happens automatically in your CI pipeline
Developer opens a pull request. CI pipeline starts.
Pipeline calls Vela API to create a CoW branch from the latest production snapshot. Available in < 30 seconds regardless of DB size.
Any schema migrations in the PR are applied to the branch database — not to staging or production.
Integration tests, E2E tests, and QA reviews all point to the isolated branch database. No conflicts with other PRs.
Pipeline deletes the branch database. Storage freed. No cleanup scripts needed.
A minimal workflow that creates a Vela database branch for each PR, runs tests, and cleans up on completion:
name: PR Database Branch
on:
pull_request:
types: [opened, synchronize, reopened, closed]
jobs:
test-with-db-branch:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create database branch
id: db-branch
run: |
BRANCH_NAME="pr-${{ github.event.pull_request.number }}"
RESPONSE=$(curl -s -X POST https://api.vela.run/v1/branches \
-H "Authorization: Bearer ${{ secrets.VELA_API_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"source": "production", "name": "'"$BRANCH_NAME"'"}')
echo "db_url=$(echo $RESPONSE | jq -r .connection_string)" >> $GITHUB_OUTPUT
- name: Run migrations
env:
DATABASE_URL: ${{ steps.db-branch.outputs.db_url }}
run: npm run db:migrate
- name: Run integration tests
env:
DATABASE_URL: ${{ steps.db-branch.outputs.db_url }}
run: npm test
cleanup-db-branch:
if: github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Delete database branch
run: |
BRANCH_NAME="pr-${{ github.event.pull_request.number }}"
curl -s -X DELETE https://api.vela.run/v1/branches/$BRANCH_NAME \
-H "Authorization: Bearer ${{ secrets.VELA_API_TOKEN }}" Actual API shape may differ — see Vela docs for current reference.
| Dimension | Branch per PR | Shared staging | Separate env per PR |
|---|---|---|---|
| Data freshness | Production snapshot (always current) | Manual refresh — often weeks stale | Manual setup — depends on team |
| Isolation | Each PR fully isolated | Shared — conflicts between PRs | Isolated but expensive |
| Setup time | < 30 seconds (CoW branch) | Already running (but problematic) | 30+ min (pg_dump/restore) |
| Storage cost | Near-zero (shared blocks) | One DB (but always conflicted) | N × full DB size |
| Scales to 50 PRs | Yes | No | Very expensive |
| Deleted automatically | Yes, on merge/close | N/A | Manual cleanup |
Branch per PR (or database preview environments) means that each pull request automatically gets its own isolated database copy — created at the start of the CI pipeline and deleted when the PR is merged or closed. This gives every PR a consistent, isolated, production-like database without conflicts with other open PRs or with the shared staging environment.
Vela exposes a REST API for database branch management. In your CI pipeline (GitHub Actions, GitLab CI, CircleCI, etc.), you add steps to call the API at the start of the job (create branch) and at the end (delete branch). The API returns a connection string for the new branch database that your tests can use.
Yes — this is a key advantage of storage-level copy-on-write branching. The branch creation time is constant regardless of database size because no data is copied. A 500 GB database creates a branch in the same time as a 1 GB database.
Schema migrations included in the PR are applied to the branch database during the CI job, not to production or shared staging. This lets you test your migration against real production data safely. If the migration causes issues, only the branch is affected — it's discarded.
Docker Compose gives you an empty database (or one seeded with synthetic fixtures). Branch-per-PR gives you a full copy of production data — real customer records, real data distributions, real edge cases. Tests that pass against empty or synthetic data can still fail against production data due to unexpected data shapes, constraint violations, or volume-related performance issues.
Try Vela's database branching in the sandbox — no infrastructure required.
Try Vela Sandbox Free