# Cutting Vercel deployment storage from 13.96 GB to 20.27 MB — previews off, retention down

> A Vercel Hobby account went over its 10 GB deployment storage limit. Deployment count was the cause, at 147 MB per build and 189 builds in thirty days.

- Source: https://oharu121.com/blog/vercel-hobby-deployment-storage-previews-retention-prune/
- Published: 2026-09-24T14:05:22+09:00
- Tags: Vercel, Developer Tooling, Claude Code

---
## Introduction

Vercel's usage page put a red ring next to Deployment Storage: 13.96 GB against a 10 GB allowance, on an account where nothing else was above 20% of its limit.

*Image: Vercel usage panel headed "Exceeded free resources", showing Deployment Storage at 13.96 GB of 10 GB with a red ring, Functions Storage at 2 GB of 10 GB, Edge Requests at 45K of 1M, and Speed Insights Events at 267 of 10K*

My first question was whether the live site was at risk. It was not, but staying over the line still costs something: Vercel stops honouring the 30-day retention window and deletes eligible deployments immediately, and it can refuse new ones until the account is back under.

The instinct was to blame the repository, since every article here carries diagrams and screenshots. However, the repository is 9.79 MiB packed, and all of its images and video together come to 9.4 MB. **One build of this site is 147 MB, and 189 of them had been created in thirty days.**

Turning preview builds off, dropping retention to a day and adding a prune to the start of the release flow took the same graph to **20.27 MB**.

## Reading the quota: 147 MB per build against 9.79 MiB of repository

Deployment Storage measures the build output Vercel keeps for every deployment, which on this site is **about fifteen times the size of the repository that produced it**.

Both numbers are one command each:

```bash
git count-objects -vH | grep size-pack
# size-pack: 9.79 MiB
```

```bash
du -sm .vercel/output
# 147	.vercel/output
```

The second one is what the quota counts. `.vercel/output` is what `vercel build` produces and `vercel deploy --prebuilt` uploads, and the deployment's file tree holds 2847 files against 2843 locally, so nothing of consequence is added in transit.

### Where the 147 MB sits

**Half of every deployment is the server bundle, and 20 MB of that half is a second copy of it.**

*Figure — BuildBreakdown: One build of this site, by directory. The server bundle appears twice, and `static/_astro` is 893 generated image derivatives across three locales.*

Inside `_render.func` sits 29 MB of `node_modules`, which is the Astro server runtime and everything the article pages import at render time. `static/_astro` is where Astro writes the processed images, and **at 893 files it is three locales' worth of derivatives for the same source pictures**.

### Why moving the images off-site would have bought 6%

Every raster and video committed to this repository totals 9.4 MB, so an object-storage bucket would have addressed **one part in sixteen of a single deployment**.

```bash
find src -type f \( -name '*.mp4' -o -name '*.png' -o -name '*.jpg' -o -name '*.webp' -o -name '*.avif' \) -exec du -ch {} + | tail -1
# 9.4M	total
```

The quota counts `static/_astro`, and Astro generates those derivatives at build time from whatever the source happens to be. Serving the originals from a bucket moves 9.4 MB out of git and leaves the 33 MB of derivatives where they are.

**Even halving a 147 MB deployment leaves the space it occupies stacking up with every deployment**, so managing the size of one deployment is not where this gets solved.

## 189 deployments in thirty days

Storage is build size times deployment count, and **the count was the term nobody had looked at**.

*Figure — QuotaArithmetic: The product that fills the quota. Shrinking the 147 MB was the visible lever; the 189 was the one doing the work.*

GitHub records a deployment for each one, so the count comes from the repository rather than from Vercel:

```bash
gh api "repos/<owner>/<repo>/deployments?per_page=100" --paginate
```

Grouped by creator over the preceding thirty days, that returns 122 deployments created by `vercel[bot]` against the `Preview` environment, and 67 created by this repository's own CI against `production`.

At 147 MB each, **189 deployments is 27.8 GB pushed through a 10 GB allowance in a month**.

### The 122 previews nobody opened

**Two thirds of the deployments were previews for branches that had been merged and deleted**, and every one of them was built automatically.

The four still alive when the account went over show why. Each was the most recent preview of its branch, each of those branches had been squash-merged and deleted, and `git ls-remote --heads origin` listed none of them.

**Vercel keeps the latest preview of an *active* branch, and it had not noticed these branches had stopped existing.**

## Previews off, retention down, and a prune

Three settings, each aimed at a different term of that product: how often a deployment is created, how long it is kept, and how many are sitting there right now.

### Turning the Git integration off

Vercel's Git integration creates a deployment for every push, on every branch, by default. Each of those deployments occupies 147 MB of Deployment Storage.

This site needs none of them: production is deployed by GitHub Actions, and the deployments on other branches are not needed either.

```json title="vercel.json" ins={3}
{
  "git": {
    "deploymentEnabled": false
  }
}
```

That stops the deployments the Git integration creates on its own, on `main` and on every other branch.

### Retention is four dropdowns in Project Settings

Retention lives in the dashboard, under Project Settings, Build and Deployment, Deployment Retention Policy. Every project on this account was sitting at the plan default:

| Deployment type | Before | After |
| --- | --- | --- |
| Canceled | 30 days | 1 day |
| Errored | 30 days | 1 day |
| Pre-Production | 30 days | 1 day |
| Production | 30 days | 1 day |

**Thirty days is the Hobby maximum, so the default is also the slowest setting available.** All four went to the shortest one instead.

*Image: Vercel's Deployment Retention Policy panel with four dropdowns, each reading "1 day": Canceled Deployments, Errored Deployments, Pre-Production Deployments, and Production Deployments*

*`Pre-Production Deployments` is the row that covers previews. There is no field here for how many deployments to keep.*

> **CAREFUL**
>
> **Retention is not a delete queue.** Setting a policy marks deployments eligible; it does not remove them. Across this account, 148 of 185 live deployments were already past a 30-day retention window and still there.

`vercel usage` cannot report any of this on the Hobby plan:

```bash
pnpm dlx vercel@58 usage
# Error: Billing cost data is unavailable for 2026-09-01 through 2026-09-21.
```

The Hobby plan has no billing record, so the CLI has nothing to read. The GB figure exists only on the dashboard.

### What `--safe` protects, and what it deletes

`vercel remove <project> --safe` deletes deployments; **the same command without `--safe` deletes the project**.

```bash
pnpm dlx vercel@58 remove oharu-tech-blog --safe --yes
# > Found 8 deployments for removal in oharu121s-projects [1s]
# > Success! Removed 8 deployments [909ms]
```

`--safe` skips deployments that hold an alias, so four previews still holding a branch alias were left behind with the live one.

*Figure — TwoPassPrune: The eight removed by `--safe` and the four it spared. A preview's branch alias survives the branch it points at, so the second pass names deployments individually.*

Of the thirteen deployments this project had, the first pass removed eight and left five: the live production deployment, and four previews still holding branch aliases for branches that no longer existed. Those four came out by URL, checked against `git ls-remote` first:

```bash
pnpm dlx vercel@58 remove <url> <url> <url> <url> --yes
# > Found 4 deployments for removal in oharu121s-projects [934ms]
# > Success! Removed 4 deployments [731ms]
```

> **UH OH**
>
> **A project name in that second command deletes the project.** The second pass has no `--safe`, and `vercel remove` given a project name without it removes domains, environment variables and every stored secret along with the deployments. Only deployment URLs belong there.

## Where a prune belongs in a release

A prune runs at the start of a release, because **running it at the end deletes the deployment a rollback would target, at the moment production has just changed**.

*Figure — PrunePlacement: Pruning before the deploy leaves the previous production deployment alive across the window where a rollback is most likely. Pruning after removes it exactly then.*

## 13.96 GB to 20.27 MB

*Image: A 30-day line chart of Vercel Deployment Storage, starting just under 7 GB, peaking near 17 GB around September 8, drifting down to 14 GB by September 21, then dropping vertically to a Total of 20.27 MB on September 22*

*The vertical drop on the right is the prune. The slope before it is 189 deployments accumulating over thirty days.*

The number is what those three changes produced together: the Git integration's automatic deployments turned off, all four retention windows set to one day, and the backlog cleared with `vercel remove`.

## Summary

Deployment storage is build size times deployment count, and **the count is the easier term to be wrong about**. This account had ten projects, 185 live deployments and a 147 MB build, against a repository small enough to be irrelevant to the total.

Three checks separate a storage problem from a repository problem. `du -sm .vercel/output` gives the size of one deployment, the platform's own deployment records give the count, and **their product against the allowance says whether to make deployments smaller or to make fewer of them**.

Where the count is the problem, the settings are `git.deploymentEnabled: false` for the inflow, a retention policy for the decay, and `vercel remove --safe` for the backlog. **Only the last one frees anything today**, and it belongs at the start of a release rather than the end.

## References

- [Vercel: Deployment Storage, including what the metric counts and the retention exceptions](https://vercel.com/docs/deployment-storage)
- [Vercel: Optimize deployment storage](https://vercel.com/docs/deployment-storage/optimize)
- [Vercel: Git configuration, with the boolean and per-branch forms of deploymentEnabled](https://vercel.com/docs/project-configuration/git-configuration)
- [Vercel changelog: Hobby projects now retain fewer deployments to free up storage](https://vercel.com/changelog/hobby-projects-now-retain-fewer-deployments-to-free-up-storage)
