Outcome: Identify and remove the causes of disk space exhaustion on a Discourse multisite server, restore automated backups, and prevent recurrence.
When to use: Automated backups are failing, or
df -hshows disk usage above ~75%.
Step 1: Assess Overall Disk Usage
SSH into your multisite server, then run:
df -h
Shows all filesystems and their usage. Focus on the
/dev/sda1line — that’s your main disk. If it’s above 80% you have a problem.
du -sh /* 2>/dev/null | sort -rh | head -20
Finds the top space consumers at the root level. On a Discourse multisite,
/varwill almost always be the biggest by far.
Step 2: Drill Into Discourse’s Shared Folder
du -sh /var/discourse/shared/*/
Shows space used by each subfolder under
shared/. You’ll typically seeweb-only/dominating.
du -sh /var/discourse/shared/web-only/backups/*/
du -sh /var/discourse/shared/web-only/uploads/*/
Breaks down space per site, by database name (not domain name — see Runbook 2 for the mapping). Backups are almost always the culprit.
docker system df
Shows how much space Docker images, containers, and build cache are using.
Step 3: Map DB Names to Domains
The folder names under backups/ and uploads/ correspond to the database key names in your multisite.yml, not the domain names. Check:
cat /var/discourse/containers/app.yml
Look for the before_bundle_exec block containing the multisite.yml contents. Each entry looks like:
opensource: # ← this is the folder name you'll see in backups/ and uploads/
database: opensource_discourse
host_names:
- digitallysovereign.org # ← this is the actual domain
See Auditing and Removing Sites on a Discourse Multisite for a full process of mapping and auditing all sites.
Step 4: Delete Files for Removed/Dead Sites
Once you’ve confirmed a site is no longer needed (see Runbook 2), delete its files immediately — no rebuild required, space is freed instantly:
rm -rf /var/discourse/shared/web-only/backups/DBNAME/
rm -rf /var/discourse/shared/web-only/uploads/DBNAME/
Then verify:
df -h /
Before deleting backups for a site you’ve migrated elsewhere, confirm the new server has a working recent backup first!
Step 5: Drop Orphaned Databases
After removing a site’s files, drop its PostgreSQL database to recover additional space. Enter the data container:
cd /var/discourse
./launcher enter data
Switch to the postgres OS user (required due to peer authentication — you cannot just pass -U postgres as root):
su - postgres
psql
Then drop the databases:
\l -- list all databases first to confirm
DROP DATABASE dbname_discourse;
\l -- confirm it's gone
\q
Then exit twice to get back to the host.
Step 6: Prune Docker
Remove unused images, stopped containers, and build cache:
docker system prune -a
Typically recovers 1-2GB. Note: all images marked as “reclaimable” will be deleted, but they’ll be re-pulled automatically on the next
./launcher rebuild app.
Step 7: Rebuild the App Container
After editing app.yml (to remove dead sites from the multisite config), rebuild:
cd /var/discourse && ./launcher rebuild app
This applies your config changes, re-pulls Docker images, and runs database migrations for remaining sites. Expect 5-15 minutes downtime.
Best Practices to Prevent Recurrence
| Practice | How |
|---|---|
| Set backup retention limits | In each site’s Admin → Backups → “Maximum backups” — 5 to 7 is a good default |
| Offload backups to external storage | Configure S3-compatible backup destination, or rsync to your Hetzner BX11 storage box after each backup via cron |
| Monitor disk usage | Set up a cron job or use Discourse’s built-in admin dashboard warnings |
| Clean up test sites promptly | When you’re done with a test/sandbox site, remove it fully (see Runbook 2) — don’t leave it accumulating backups |
| Check disk usage monthly | Just run df -h and du -sh /var/discourse/shared/web-only/backups/*/ — takes 30 seconds |
(created with AI)