Storage on Render Postgres is one-way. You can grow it - manually or via autoscaling - but you can never shrink it. That asymmetry decides almost every operational habit in this step.
This is also one of the few areas where Render quietly does work for you in the background, so it pays to know exactly when and how.
How autoscaling works
flowchart LR use["Disk usage hits ~90%"] trigger["Render autoscale triggers"] grow["Grow by ~50%,<br/>rounded up to next 5 GB"] cooldown["12-hour cooldown<br/>before next growth"] max["Hard ceiling: 16 TB"] use --> trigger --> grow --> cooldown grow -.-> max
The rules:
| Knob | Behavior |
|---|---|
| Trigger threshold | Roughly ~90% disk usage |
| Growth amount | Roughly ~50%, rounded up to the next 5 GB multiple |
| Maximum | 16 TB |
| Cooldown after a growth | 12 hours before another autoscale |
| Shrink | Never - disk increases are permanent |
A worked example: a 10 GB disk that fills past ~9 GB triggers a growth to ~15 GB (10 × 1.5 = 15, already a 5 GB multiple). The next growth would happen at ~13.5 GB usage, no sooner than 12 hours after the first one.
If the rate of growth is faster than autoscaling can keep up - say your app suddenly inserts gigabytes per hour - you can hit the limit between autoscale events and degrade. We cover what that looks like below.
What “can never shrink” actually means
flowchart LR s1["10 GB"] -->|"fills"| s2["15 GB<br/>(autoscaled)"] s2 -->|"app deletes data"| s3["Still 15 GB<br/>(disk doesn't shrink)"] s3 -->|"export + recreate"| s4["10 GB on new instance"]
If you delete data from a Postgres table, three things happen:
- The rows are marked dead but stay on disk until
VACUUMreclaims them. - After
VACUUM, the table-level free space is reusable for future inserts. - The disk-level allocation doesn’t change. Render’s view of the disk is still 15 GB.
So “shrinking” in any meaningful sense means archiving data and recreating the database on a smaller disk. That’s an export-restore migration, not an in-place operation. The same flow as changing an immutable field, covered in step 02.
The only way to get a smaller disk number is to be on a fresh instance. So the operational habit:
Archive aggressively before you autoscale, not after.
Suspension at the hard limit
Autoscaling has the 16 TB ceiling and the 12-hour cooldown. If your disk fills past the configured limit faster than autoscaling can react - or you’re at 16 TB - Render may suspend the database until you resolve the situation.
A suspended database accepts no connections. Your app sees connection failures. The fix is to:
- Stop the writes that are filling the disk Pause workers that produce data. Pause batch jobs. Whatever’s adding bytes, stop it.
- Vacuum to reclaim freed space If you’ve deleted rows,
VACUUM(orVACUUM FULLfor aggressive cases - note it locks the table) makes the freed space reusable. - Archive cold data
COPYhistorical rows to S3 or another store, thenDELETEandVACUUM. - Manually grow the disk Up to the plan’s maximum if autoscaling isn’t fast enough.
You don’t want to be doing any of this under pressure. The next section is how to avoid it.
How to monitor disk before it hurts
Three layers of monitoring, in order of how often you’ll use them:
1. Render Dashboard graphs
The Postgres instance page in the Render Dashboard has a Storage graph that plots disk usage over time. Glance at it weekly. The trend line tells you if you’re going to hit autoscale next month or in six months.
2. The metrics API / MCP
For programmatic monitoring (alerts, dashboards), use Render’s metrics endpoints. Via the MCP server:
get_metrics( resourceId: "<postgres-id>", metricTypes: ["disk_usage_percent"])You can hit a similar endpoint via the API directly if you’re not using MCP. Wire it into your existing alerting (Datadog, Grafana, plain cron + Slack webhook) so you get notified at, say, 70% usage - well before autoscale even thinks about firing.
3. SQL: pg_database_size and table-level breakdown
When you need to know what’s using the space:
SELECT pg_size_pretty(pg_database_size(current_database())) AS db_size;For a per-table breakdown:
SELECT schemaname, relname, pg_size_pretty(pg_total_relation_size(relid)) AS total_size, pg_size_pretty(pg_relation_size(relid)) AS table_size, pg_size_pretty(pg_total_relation_size(relid) - pg_relation_size(relid)) AS index_and_toast_sizeFROM pg_catalog.pg_statio_user_tablesORDER BY pg_total_relation_size(relid) DESCLIMIT 20;The result is sorted largest-first. Tables at the top are the ones to archive or partition. Indexes and TOAST data (large field storage) are part of the total - sometimes the index is bigger than the table.
The pre-autoscale playbook
When usage starts climbing toward 70-80%, you have time to act before autoscale kicks in. Three moves, in order:
- Check for bloat Run the
pg_stat_user_tablesquery from the operations step. Highn_dead_tupmeans autovacuum is falling behind. A targetedVACUUM ANALYZEreclaims space without growing the disk. - Drop unused indexes Indexes you never query are pure cost.
pg_stat_user_indexesshows usage. The “indexes never scanned” query (in step 09) finds them quickly. - Archive old data Move cold rows out - to S3, to a separate “archive” Postgres instance, or to a partitioned table you can detach later. Then
DELETE+VACUUM.
Step 3 is the only one that frees real disk. The first two free internal space - Postgres can reuse it, but the disk allocation stays the same. That’s still useful: it pushes out the day you’d autoscale.
Setting initial size and the manual growth knob
You picked an initial size at creation (diskSizeGB in the Blueprint, or a slider in the Render Dashboard). You can manually grow it any time, on any plan:
databases: - name: app-db plan: basic-1gb diskSizeGB: 20Increase the number, sync, Render grows the disk in the background. There’s no downtime for storage growth - the database keeps serving traffic while the volume resizes.
A worked sizing decision
A common conversation:
“Should I provision 10 GB or 50 GB to start?”
The math, since storage is mostly variable cost based on what you allocate:
- 10 GB: small bill day one, but you’ll likely autoscale within months. Each autoscale costs nothing extra in dollars (Render bills the actual size you have), but you can only autoscale once per 12 hours, and the rounding is conservative.
- 50 GB: larger bill day one, but you can run for a year or more with no surprises.
The right answer for most production apps: start at the size that covers your next 3-6 months of growth. Autoscaling exists for the case where your 6-month estimate is wrong, not as the primary growth strategy.
For dev/staging databases, the minimum (1 GB) is almost always fine. They rarely accumulate enough data to autoscale.
What you learned
- Storage autoscales at ~90% usage by ~50% (rounded to next 5 GB) up to 16 TB, with a 12-hour cooldown between growths
- Disks can grow but never shrink - getting a smaller disk requires export + restore to a new instance
- Monitor proactively: Render Dashboard graph for trends, MCP `get_metrics` for alerts, `pg_database_size` and `pg_total_relation_size` for what's eating the space
- `VACUUM` reclaims internal space (reusable for future inserts) but doesn't shrink the disk allocation
- Pre-autoscale moves: vacuum dead tuples, drop unused indexes, archive cold data. Only archiving frees real disk; the others delay the next autoscale