My Next.js app runs on Vercel with a Neon Postgres database connected through the Vercel-managed Neon integration. In late October every preview deployment started to fail about a second after it was created. There was no build log at all, and production deployments kept working. Vercel showed one line:
BUILD_FAILED
Resource provisioning failed
The cause was the Neon free plan's limit of 10 branches per project. Below is how I confirmed it, how fast the project filled up again after I emptied it, and what to do before you delete anything.
Where Neon's reason shows up
With this integration, each preview deployment asks Neon for a database branch named preview/<git-branch>, a copy-on-write copy of the production branch. That request runs as an integration step before the build starts (Neon docs). The production branch counts toward the 10, so nine previews fill the project. When the project is full, Neon refuses the new branch and Vercel reports the whole step as "Resource provisioning failed".
The first time I hit this, in September, I found Neon's own reason in the deployment's integration actions, read through the Vercel REST API (GET /v1/deployments/<deployment-id>/integrations). The status text there said Branch limit reached. Counting the branches is faster:
npx neonctl branches list --project-id "$NEON_PROJECT" --output json | jq 'length'
# who made them and when
npx neonctl branches list --project-id "$NEON_PROJECT" --output json \
| jq -r '.[] | [.created_at, .creation_source, .name] | @tsv'
If the first command prints 10 and most rows say vercel and preview/..., that's your answer. Other people on the Vercel community forum traced the same error to the same limit. Some threads report the error with free slots left, so check the count first. Don't assume it.
How fast ten branches fill
My repository gets a lot of short pull requests. In October, up to the 27th, 484 of the project's 851 Vercel builds were previews. Every new git branch that got a preview deployment also got a Neon branch.
On 28 October I emptied the project at about 05:02 UTC, leaving only production. This is what happened next:
Nine new preview branches appeared between 05:03 and 14:11 UTC, all with creation_source: vercel. The project was full again 9 hours 8 minutes after I emptied it. I deleted nine more at 18:29 to 18:31 UTC, and by 01:45 UTC the next morning five new ones had arrived.
The same cleanup came up every day that week. These are the preview branches I deleted by hand:
| Day (UTC) | Preview branches deleted |
|---|---|
| 24 October | 9 |
| 25 October | 6 |
| 26 October | 3 |
| 27 October | 5 |
| 28 October | 9 in the morning, 9 in the evening |
That's 41 branches in five days. The ones I looked at in the listing had each kept a compute endpoint active for about 300 seconds (active_time_seconds).
Merging the pull request doesn't free the branch
Every branch I deleted belonged to a pull request that was already merged, and its git branch was usually gone. With the Vercel-managed integration, Neon deletes a preview branch when the branch's Vercel deployments are deleted, and Vercel keeps preview deployments for 180 days by default (Neon docs). A merge doesn't delete deployments, so with that default retention the database branch stays for months.
Skipping the build doesn't skip the branch
My first fix was to stop building previews with Vercel's ignored build step in apps/web/vercel.json. The first version also cancelled a push to main, because VERCEL_ENV didn't read as production in that step. The second version tests the branch name:
// first try: also skipped production
"ignoreCommand": "[ \"$VERCEL_ENV\" != production ]"
// second try: build main, and any deploy whose branch is unknown
"ignoreCommand": "[ -n \"$VERCEL_GIT_COMMIT_REF\" ] && [ \"$VERCEL_GIT_COMMIT_REF\" != main ]"
The builds were skipped, but the database branches kept coming. All 14 branches in the chart were created after this step was in place. The integration step runs before the ignored build step, so a skipped preview still provisions its branch, and when the project is full it fails the same way. The ignored build step saves build minutes. It does nothing for the branch count.
Back up before you delete
A preview branch may hold data that a preview wrote, so I dumped each one before deleting it. One catch: pg_dump refuses to dump a server with a newer major version than its own (the error and the rule). My Neon project runs Postgres 17 and my Mac had pg_dump 16.15, so I installed the 17 client first. A loop like this one does the rest:
brew install postgresql@17
PG_DUMP="$(brew --prefix postgresql@17)/bin/pg_dump" # 17.11 here
npx neonctl branches list --project-id "$NEON_PROJECT" --output json \
| jq -r '.[] | select(.name | startswith("preview/")) | "\(.id) \(.name)"' \
| while read -r id name; do
uri=$(npx neonctl connection-string "$id" --project-id "$NEON_PROJECT")
"$PG_DUMP" -Fc -f "backup/${name//\//__}.dump" "$uri"
done
The preview branches I listed showed about 52 MB of logical size each, the same as production, since it starts as a copy. The nine dumps came to 27.7 MB in all, between 3.03 and 3.08 MB each, and finished 41 to 48 seconds apart. Only after that did I delete the branches, by id:
npx neonctl branches delete "$id" --project-id "$NEON_PROJECT"
The same limit breaks your CI
Before a schema migration merges, I rehearse it on a throwaway branch copied from production, then delete the branch. Reduced to its shape:
const created = neon(['branches', 'create', '--parent', MAIN, '--name', name]);
const id = JSON.parse(created.stdout).branch.id;
try {
// prisma migrate deploy against the copy, then compare the schema
} finally {
neon(['branches', 'delete', id]); // also when a step fails
}
The finally keeps the rehearsal from leaking branches. It can't help when previews have taken every slot: on several days in the table the rehearsal failed at branches create until I cleared space by hand.
What stops the refill
Deleting branches only buys hours. The setting that stops the refill is in the integration: Neon's docs describe a "Create database branch for deployment" choice with Preview and Production boxes when you connect the project. With Preview off, a preview deployment gets no branch of its own, so check which DATABASE_URL your Preview environment uses then. It must not be production. Two other levers: shorten Vercel's retention for preview deployments, so the branches go with them, or delete the branch from CI when the pull request closes.
I haven't measured the fix yet. At 01:45 UTC on 29 October preview branching was still on and the project had 6 of 10 branches. My next step is to switch it off and check that the count stays at 1.
Checklist
1. neonctl branches list | jq length -> 10 means the limit, not your code
2. pg_dump at the server's major version -> back up the preview branches
3. delete by id, previews of merged PRs only
4. turn off "Create database branch for deployment" for Preview,
or shorten preview retention, or delete branches on PR close
5. leave slots for CI throwaway branches

Top comments (0)