Server Storage Planning for Self-Hosting

Why Storage Planning Matters

Running out of disk space is the most common preventable failure in self-hosting. Your Jellyfin media library grows. Your Immich photo backups accumulate. Your Nextcloud file sync eats gigabytes without warning. One day, a Docker container crashes because /var is full, and half your services go down.

Server storage planning prevents this. Before you install a single container, you should know how much space you need, where your data will live, what file system to use, and how you will expand when you outgrow your initial setup. Getting this right upfront saves you from painful data migrations later.

The core rule: separate your OS drive from your data storage. Your operating system and Docker images belong on a small, fast SSD. Your media, photos, files, and database volumes belong on separate, larger drives. This separation makes backups simpler, upgrades cleaner, and disk-full failures less catastrophic.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Basic terminal comfort (Linux Basics)
  • Understanding of Docker volumes (Docker Volumes)
  • Familiarity with your planned self-hosted applications

Estimating Your Storage Needs

Storage needs vary wildly depending on what you self-host. Here are realistic estimates by use case.

Media Server (Jellyfin, Plex, Emby)

Media libraries are the largest storage consumers in self-hosting. Plan generously.

Content TypeAverage SizeExample
Movie (1080p)4-8 GB100 movies = 400-800 GB
Movie (4K HDR)20-60 GB100 movies = 2-6 TB
TV episode (1080p)1-3 GB500 episodes = 500 GB - 1.5 TB
Music album (FLAC)300-500 MB1,000 albums = 300-500 GB
Music album (MP3 320kbps)80-120 MB1,000 albums = 80-120 GB
Audiobook200-500 MB200 books = 40-100 GB

Starting recommendation: 4 TB minimum for a modest media library. 8-12 TB if you collect 4K content. Plan for growth — media libraries only get bigger.

Photo Management (Immich, PhotoPrism)

Photos add up faster than most people expect, especially with modern phone cameras shooting 12-50 MP images and 4K video.

Content TypeAverage SizeExample
Smartphone photo3-8 MB10,000 photos = 30-80 GB
RAW photo (DSLR)25-60 MB10,000 RAW files = 250-600 GB
4K video (1 min)300-400 MB100 minutes = 30-40 GB
Immich ML thumbnails~20% of originals80 GB originals ≈ 16 GB thumbnails

Starting recommendation: 500 GB for phone photo backup of a single person. 1-2 TB for a family. Double it if you shoot RAW or lots of video.

File Sync (Nextcloud, Seafile)

File sync storage depends entirely on how many users you have and what they store.

Use CaseEstimate
Single user, documents only10-50 GB
Single user, documents + projects50-200 GB
Family (4 users)200 GB - 1 TB
Small team (10 users)500 GB - 2 TB

Starting recommendation: 500 GB covers most home setups. Nextcloud also stores versions and trash, so expect 30-50% overhead on top of your actual file size.

Backups (Duplicati, BorgBackup, Restic)

Your backup strategy determines backup storage needs. With deduplication and compression (which Borg and Restic handle automatically), backup storage is smaller than you might think.

StrategyStorage Needed
Full backup, no dedup1x source data per copy
Incremental with dedup (Borg/Restic)1.2-1.5x source data for 30+ snapshots
Database dumps only1-10 GB per database, compressed

Starting recommendation: Allocate 1.5x your total data for local backups with deduplication. Off-site backups are a separate concern — see Backup Strategy: The 3-2-1 Rule.

The 2x Rule

Whatever you estimate, double it. Storage needs grow faster than you expect. Buying a 4 TB drive when you think you need 2 TB is cheaper than migrating data to a bigger drive six months later.

Plan for at least 2x your current needs as a minimum. 3x is better if budget allows.

Storage Types

HDD vs SSD

FactorHDDSSD
Cost per TB$15-25$60-100
Sequential read/write150-250 MB/s500-3,500 MB/s
Random IOPS50-20010,000-100,000+
Power consumption5-10W2-5W
NoiseAudible (spinning platters)Silent
Lifespan3-5 years typical5-10 years (depends on writes)
Best forLarge media, backups, archivesOS, databases, Docker images, app data

The recommendation: Use an SSD for your OS drive and Docker root (/var/lib/docker). Use HDDs for bulk storage — media libraries, photo archives, backups. This gives you fast container performance where it matters and cheap bulk storage where speed is less important.

For database-heavy workloads (Nextcloud, Immich, BookStack), put database volumes on the SSD. Database random I/O performance on an HDD is painful.

Internal vs External

Internal drives (SATA or NVMe) are always preferred for a home server. They are faster, more reliable (no USB controller bottleneck or cable disconnection), and draw power directly from the PSU.

External USB drives are acceptable for:

  • Backup destinations (not primary storage)
  • Temporary overflow
  • Raspberry Pi setups where internal drives are not an option

Never use an external USB drive as primary storage for Docker volumes. USB disconnections cause data corruption.

DAS vs NAS

TypeWhat It IsBest For
DAS (Direct-Attached Storage)Drives connected directly to your serverSingle server setups
NAS (Network-Attached Storage)Dedicated storage device on your networkMulti-server setups, centralized storage

For most self-hosting beginners: DAS. Plug drives directly into your server. Simpler, faster, fewer failure points.

Graduate to a NAS when you have multiple servers that need access to the same storage, or when you want a dedicated storage appliance with drive management features. See NAS Basics for a full comparison.

File Systems

Your file system choice affects performance, data safety, and available features. Here are the options worth considering.

ext4

The default Linux file system. Proven, stable, fast.

ProsCons
Rock-solid stability (20+ years of production use)No built-in checksumming
Excellent performanceNo native snapshots
Every Linux tool supports itNo built-in compression
Simple to manageMaximum volume size: 1 EiB (effectively unlimited)
Low overhead

Use ext4 when: You want reliability without complexity. This is the right choice for single-disk setups, OS drives, and any scenario where you do not need ZFS or Btrfs features.

Format a drive with ext4:

sudo mkfs.ext4 -L datastore /dev/sdb1

XFS

High-performance file system. Default on RHEL/CentOS. Excellent for large files.

ProsCons
Excellent large-file performanceCannot shrink a partition (only grow)
Handles many files efficientlySlightly more complex recovery
Good for media storage

Use XFS when: You primarily store large files (media, backups) and want maximum sequential throughput.

ZFS

The most feature-rich file system available on Linux. Built-in RAID, checksumming, compression, snapshots, and self-healing. Also the most complex.

ProsCons
End-to-end data integrity (checksumming)RAM hungry (1 GB per TB of storage is the rule of thumb)
Built-in RAID (RAIDZ1, RAIDZ2, mirrors)Cannot add single drives to an existing pool easily
Native snapshots and clonesSteeper learning curve
Transparent compressionLicense incompatibility with Linux kernel (must use DKMS/module)
Send/receive for backups

Use ZFS when: You have 8+ GB of RAM, multiple drives, and you value data integrity above all else. ZFS is the gold standard for NAS and storage server builds. See ZFS Basics for setup details.

Btrfs

Linux-native copy-on-write file system. Offers many of ZFS’s features with less RAM overhead.

ProsCons
Native snapshots and subvolumesRAID 5/6 mode is still unreliable (do not use it)
Transparent compression (zstd)Less battle-tested than ZFS for large arrays
Built into the Linux kernelPerformance can degrade with heavy fragmentation
Lower RAM requirements than ZFS

Use Btrfs when: You want snapshots and compression on a single drive or a RAID 1 mirror, without ZFS’s RAM requirements.

The Recommendation

Use ext4 unless you have a specific reason to use something else. It is the most reliable, best-supported, and simplest option. Graduate to ZFS when you build a multi-drive storage server and want data integrity guarantees. Avoid Btrfs RAID 5/6 entirely.

ScenarioFile System
Single OS driveext4
Single data driveext4
2-drive mirrorZFS mirror or ext4 + mdadm RAID 1
4+ drive storage serverZFS (RAIDZ1 or RAIDZ2)
Raspberry Pi SD card / USBext4
NAS applianceWhatever the NAS OS defaults to (usually Btrfs or ext4)

RAID vs No RAID

RAID protects against drive failure. It does not replace backups. See RAID Explained for the full breakdown.

When You Need RAID

  • You have irreplaceable data that would take significant effort to restore
  • You cannot tolerate downtime while restoring from backup
  • You have 2+ drives available

When You Do Not Need RAID

  • You have a solid backup strategy and can tolerate restoring from backup
  • You are on a tight budget (money is better spent on backup drives)
  • You only have one drive

The Recommendation

For a first home server with one or two drives: skip RAID, invest in good backups. A single drive with automated off-site backups (Backup Strategy) is more resilient than a RAID array with no backups.

For a storage server with 3+ drives: use RAID (ZFS RAIDZ1 for 3-4 drives, RAIDZ2 for 5+ drives). But still maintain backups — RAID does not protect against accidental deletion, ransomware, or fire.

Docker Volume Storage Strategy

A well-planned directory layout makes backups, migrations, and troubleshooting vastly easier. Here is the structure that works.

/                          # OS drive (SSD)
├── /var/lib/docker/       # Docker images, layers, build cache (SSD)
├── /opt/appdata/          # App configs and small databases (SSD)
│   ├── immich/
│   │   ├── db/
│   │   └── config/
│   ├── jellyfin/
│   │   ├── config/
│   │   └── cache/
│   ├── nextcloud/
│   │   ├── db/
│   │   └── config/
│   └── vaultwarden/
│       └── data/
└── /mnt/data/             # Bulk storage (HDD, mounted separately)
    ├── media/
    │   ├── movies/
    │   ├── tv/
    │   └── music/
    ├── photos/
    ├── files/
    └── backups/

Key principles:

  1. /opt/appdata/ on the SSD holds application configs and databases — small, frequently accessed, performance-sensitive.
  2. /mnt/data/ on the HDD holds bulk content — media libraries, photo originals, synced files. Large, sequential access, tolerant of slower drives.
  3. One subdirectory per app under /opt/appdata/. Makes it obvious which data belongs to which container.

Docker Compose Example

Here is how this layout looks in a Docker Compose file:

services:
  jellyfin:
    image: jellyfin/jellyfin:10.9.6
    volumes:
      - /opt/appdata/jellyfin/config:/config     # SSD — config and metadata
      - /opt/appdata/jellyfin/cache:/cache        # SSD — transcoding cache
      - /mnt/data/media:/media:ro                 # HDD — media library (read-only)
    restart: unless-stopped

  immich-server:
    image: ghcr.io/immich-app/server:v1.99.0
    volumes:
      - /mnt/data/photos:/usr/src/app/upload      # HDD — photo originals
    restart: unless-stopped

  immich-db:
    image: tensorchord/pgvecto-rs:pg16-v0.2.1
    volumes:
      - /opt/appdata/immich/db:/var/lib/postgresql/data  # SSD — database (IOPS-sensitive)
    restart: unless-stopped

Notice the pattern: database on SSD, bulk files on HDD, config on SSD. This is not accidental — it matches each data type to the storage tier where it performs best.

Creating the Directory Structure

# Create app data directory on SSD
sudo mkdir -p /opt/appdata

# Create bulk storage mount point
sudo mkdir -p /mnt/data

# After mounting your data drive (see next section):
sudo mkdir -p /mnt/data/{media,photos,files,backups}
sudo mkdir -p /mnt/data/media/{movies,tv,music}

# Set ownership (adjust UID/GID as needed for your containers)
sudo chown -R 1000:1000 /opt/appdata
sudo chown -R 1000:1000 /mnt/data

Mounting External Storage

Once you have your data drive, you need to mount it persistently so it survives reboots.

Find Your Drive

# List all block devices
lsblk -f

# Example output:
# NAME   FSTYPE LABEL     MOUNTPOINT  SIZE
# sda                                 256G
# ├─sda1 ext4   os-root   /           250G
# └─sda2 swap             [SWAP]        6G
# sdb                                   4T
# └─sdb1 ext4   datastore             4.0T

Partition and Format (New Drive)

If the drive is new and unformatted:

# Create a single partition (GPT)
sudo parted /dev/sdb mklabel gpt
sudo parted /dev/sdb mkpart primary ext4 0% 100%

# Format with ext4
sudo mkfs.ext4 -L datastore /dev/sdb1

Get the UUID

Always mount by UUID, never by device name (/dev/sdb1). Device names can change between reboots; UUIDs do not.

sudo blkid /dev/sdb1
# Output: /dev/sdb1: LABEL="datastore" UUID="a1b2c3d4-e5f6-7890-abcd-ef1234567890" TYPE="ext4"

Add to /etc/fstab

Edit /etc/fstab to mount the drive automatically on boot:

# /etc/fstab entry for data drive
UUID=a1b2c3d4-e5f6-7890-abcd-ef1234567890  /mnt/data  ext4  defaults,noatime  0  2

The noatime option disables access time updates, which reduces unnecessary writes — especially useful on SSDs and for Docker volume performance.

Mount and Verify

# Mount everything in fstab
sudo mount -a

# Verify
df -h /mnt/data
# Output:
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/sdb1       3.6T   28K  3.4T   1% /mnt/data

Mounting NFS Shares

If your bulk storage lives on a NAS, mount it via NFS:

# Install NFS client
sudo apt install nfs-common

# /etc/fstab entry for NFS share
192.168.1.50:/volume1/docker  /mnt/data  nfs  defaults,_netdev,soft,timeo=30  0  0

The _netdev option tells Linux to wait for the network before mounting. Without it, your server can hang on boot if the NAS is unreachable.

Monitoring Disk Usage

Running out of disk space without warning is negligent. Monitor your storage and set alerts.

Quick Checks

# Disk usage summary for all mounted volumes
df -h

# Top 10 largest directories under /opt/appdata
du -h --max-depth=1 /opt/appdata | sort -rh | head -10

# Docker disk usage breakdown
docker system df

# Detailed Docker disk usage
docker system df -v

Finding Large Files

# Find files over 1 GB in your data directory
find /mnt/data -type f -size +1G -exec ls -lh {} \; 2>/dev/null

# Find the largest Docker volumes
du -h --max-depth=2 /var/lib/docker/volumes | sort -rh | head -10

Docker Cleanup

Docker accumulates unused images, stopped containers, and dangling volumes over time. Reclaim space periodically:

# Remove unused images, stopped containers, and build cache
docker system prune -a

# Remove unused volumes (CAREFUL — only if you know they're unused)
docker volume prune

Warning: docker volume prune deletes unnamed volumes that are not attached to any container. If you use named volumes, verify they are truly unused before pruning.

Automated Monitoring

For persistent monitoring, set up Uptime Kuma or Beszel with disk usage alerts. At minimum, add a cron job that warns you:

# /etc/cron.daily/disk-check
#!/bin/bash
THRESHOLD=85
USAGE=$(df /mnt/data --output=pcent | tail -1 | tr -d ' %')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
  echo "WARNING: /mnt/data is ${USAGE}% full" | mail -s "Disk Alert" [email protected]
fi

Set your threshold at 85%. This gives you time to react before hitting 100%.

Common Mistakes

Using /dev/sdb instead of UUID in fstab. Device names shift when you add or remove drives. UUID is stable. Always use UUID= in your fstab entries.

Putting everything on one drive. When your single drive fills up, your OS, Docker, and all your apps fail simultaneously. Separate your OS drive from data storage. When the data drive fills up, your OS and Docker engine still function normally.

Using :latest Docker images on a small SSD. Docker pulls new image layers with each :latest update, and old layers accumulate. Pin image versions and run docker system prune periodically to reclaim space. See Docker Volumes for more on managing Docker storage.

Not monitoring disk usage. A drive at 98% capacity does not send you an email. It silently corrupts your database when a write fails. Set up alerts well before you run out of space.

Choosing ZFS on a 4 GB RAM machine. ZFS wants memory for its ARC cache. On a low-RAM system (4 GB or less), ZFS can starve your containers of memory. Use ext4 on resource-constrained hardware.

Mounting USB drives for primary Docker volumes. USB drives disconnect. When they do, Docker writes to the mount point on your root filesystem instead, filling up your OS drive. Use USB drives for backups only.

Skipping noatime in fstab. Every file access updates the access timestamp, generating unnecessary write I/O. Adding noatime is free performance, especially for Docker volumes with frequent reads.

Forgetting _netdev for network mounts. Without this flag, your server tries to mount the NFS/CIFS share before the network is up. The boot process hangs for minutes until the mount times out.

Next Steps

With your storage planned and mounted, you are ready to:

  1. Set up Docker volumes that map to your storage layout — Docker Volumes
  2. Configure RAID if you have multiple drives — RAID Explained
  3. Deploy your first app with proper volume mounts — Docker Compose Basics
  4. Implement backups so storage failure does not mean data loss — Backup Strategy: The 3-2-1 Rule
  5. Explore NAS options if you want centralized storage — NAS Basics
  6. Set up ZFS for advanced data integrity — ZFS Basics

FAQ

How much storage do I need to start self-hosting?

A 256 GB SSD for the OS and Docker, plus a 2-4 TB HDD for data, handles most beginner setups. This covers a media server, photo management, file sync, and backups. Apply the 2x rule — buy twice what you think you need today.

Should I use SSDs for everything?

No. SSDs make sense for your OS drive, databases, and application configs where random I/O performance matters. Bulk media, photos, and backups are sequential workloads where HDDs perform fine at a fraction of the cost. A 4 TB SSD costs $200-400; a 4 TB HDD costs $60-100.

Can I add more storage later without starting over?

Yes, but how easily depends on your setup. With separate mount points (/mnt/data, /mnt/data2), you can add a new drive and mount it alongside existing storage. With LVM, you can extend a logical volume across multiple drives. With ZFS, you can add new vdevs to an existing pool. The simplest approach: start with a single data drive, and when you outgrow it, add a second drive at a new mount point and move specific workloads to it.

Is NFS reliable enough for Docker volumes?

For media streaming and file storage, NFS works well. For databases, no. Database I/O requires low latency and strong consistency guarantees that NFS cannot reliably provide. Keep database volumes on local drives and use NFS for bulk content like media libraries and photo archives.

What happens when a drive fails?

Without RAID: the data on that drive is gone. You restore from backup. With RAID: the array continues operating in degraded mode while you replace the failed drive. Either way, you need backups. RAID buys you uptime; backups buy you recoverability. See RAID Explained and Backup Strategy.