← Field Notes
Published Last updated 12 min read

The Storage Host Became a Contract, Not Just a Server

Separating storage from application compute forced me to define ownership, permissions, failure boundaries, and recovery instead of treating shared disks as a transparent dependency.

In this article
  1. A second machine creates a boundary, not high availability
  2. The real design is the storage contract
  3. ZFS gives me storage features, not immunity
  4. NFS turned identity into part of the architecture
  5. The dependency has four layers
  6. 1. Storage health
  7. 2. Export health
  8. 3. Client mount identity
  9. 4. Application mapping and permissions
  10. The storage host needs its own health model
  11. Small hardware can own a large blast radius
  12. Separation made maintenance easier to reason about
  13. What I would define before building it again
  14. The second server changed the question
  15. The authoritative copy must be explicit
  16. Performance belongs to the entire path
  17. Organization is part of recovery
  18. AI transparency

Moving data to another machine did not make storage disappear from the architecture. It made the storage contract impossible to ignore.

A second server can look unimpressive on a hardware list.

It may have less compute, fewer services, and none of the workloads that make a dashboard interesting. But once that machine owns shared storage, its importance stops being proportional to its benchmark score.

That is what changed in my homelab.

The secondary host became useful not because it gave me another place to run containers, but because it took ownership of a responsibility that had become too important to blur into the application layer: authoritative shared data.

That separation made the environment easier to reason about. It also created a dependency that I had to describe honestly.

The storage host is not “just where the files live.” It owns a contract with every system that consumes those files.

A second machine creates a boundary, not high availability

With one host, a storage problem and an application problem can become the same incident.

A disk issue, an operating-system update, a container-runtime change, and an application restart all share the same physical blast radius. Even when the underlying causes are different, the symptoms can collapse into one question: why is everything broken?

Separating storage gives me more useful failure boundaries.

If an application fails while the storage layer remains healthy, I can investigate the application path without simultaneously questioning whether the authoritative data disappeared. If the storage layer fails, I know which dependent services should be expected to degrade.

That is valuable.

It is not automatic failover.

If the storage host becomes unavailable, applications that depend on it still lose that dependency. I have not built a clustered storage platform that transparently moves ownership elsewhere. Recovery requires intervention.

That distinction matters because a diagram with two boxes can look more resilient than the implementation actually is.

The architecture gives me separation and recoverability, not magic continuity.

I made a similar distinction when describing the compute side in The Application Host Is Replaceable on Purpose. The application machine can be rebuilt because authoritative bulk data is not supposed to be trapped inside it. The storage host is the other half of that promise.

The real design is the storage contract

Once multiple systems depend on shared data, the design needs more precision than “mount this folder.”

For each shared dataset, I want a written answer to a small set of questions:

QuestionWhy it matters
Who owns the authoritative copy?Prevents two systems from silently becoming competing sources of truth
What is the dataset for?Keeps unrelated data from collecting under one broad share
Which clients should receive access?Limits exposure to the systems that actually need it
Which consumers need read/write access?Avoids granting write access merely because it is convenient
Which identity creates new files?Makes ownership and permissions predictable
What happens when the storage disappears?Defines expected failure behavior instead of discovering it during an outage
What protects the data beyond the local pool?Separates storage resilience from backup and disaster recovery
How is restoration validated?Turns recovery from a theory into an operational capability

That table is more useful to me than a detailed topology diagram filled with machine names and addresses.

The identifiers change. The contract survives.

ZFS gives me storage features, not immunity

The storage layer uses ZFS because its model fits what I want from a system responsible for important data: datasets, checksums, snapshots, pool health reporting, scrubs, and flexible replication workflows.

OpenZFS documents end-to-end checksums as a mechanism for detecting corruption, and a scrub verifies stored data against those checksums. On redundant pool layouts, ZFS can repair some detected damage from a good replica. The current OpenZFS documentation also exposes detailed pool health through zpool status. (OpenZFS checksums, zpool scrub, zpool status)

Those capabilities are excellent reasons to trust ZFS with a storage role.

They are not reasons to stop thinking about backup.

A healthy pool does not protect me from every destructive event. A valid write can still delete the wrong file. A compromised account can still modify data it is authorized to reach. A synchronization mistake can still propagate bad state. A host can still be stolen, damaged, or lost with everything physically attached to it.

Snapshots can narrow some recovery windows, but a snapshot that lives only on the same system is still inside that system’s failure domain.

So I separate three ideas that are easy to blend together:

  1. Integrity: can the storage detect corruption and report health?
  2. Local recoverability: can I recover from mistakes or limited failures using local redundancy or snapshots?
  3. Backup/disaster recovery: do I have another protected copy and a tested way to restore it when the storage host itself is no longer trustworthy or available?

A good filesystem helps with the first two.

It does not automatically complete the third.

NFS turned identity into part of the architecture

The application layer consumes selected storage over NFS.

That sounds simple until permissions matter.

On Linux, filesystem ownership is still expressed through user and group identities. NFS then adds export policy and identity-mapping behavior on top of that. The current exports(5) documentation makes the boundary explicit: an export can be limited to specific clients, can be read-only or read/write, and applies root-squashing behavior by default unless configured otherwise. (Linux exports(5))

That means a path can be reachable and still be wrong for the application.

I can have all of these conditions at once:

  • The storage pool is healthy.
  • The export exists.
  • The client can mount it.
  • The expected directory is visible.
  • The application still cannot create or modify what it needs.

The failure may be identity, ownership, group membership, export policy, mount behavior, or the identity used inside a container.

That changed how I think about storage permissions.

They are not a cleanup task after the share works.

They are part of the design.

For each writable dataset I want to know which identity should create files, which group represents the shared access boundary, which consumers need writes, and which should be read-only.

“Make it writable by everything” is not a storage strategy. It is a way to hide the permission model until a later incident makes it painful again.

The dependency has four layers

When an application says its data is missing, I do not want to troubleshoot from the application inward anymore.

I want to validate the dependency from the bottom up.

1. Storage health

Is the expected pool imported and healthy? Are the relevant datasets available? Are there device errors, degraded redundancy, or an active recovery operation that changes the situation?

This layer proves that the storage system itself can present the data.

2. Export health

Is the intended dataset actually being offered to the intended client scope with the intended access mode?

This layer proves that the server is willing to provide the data over the network.

3. Client mount identity

Did the client mount the filesystem I think it mounted?

This is where I learned not to trust a directory simply because the directory exists. A local directory can remain present after a remote filesystem disappears, which means an application can continue writing into the wrong filesystem while looking superficially healthy.

I covered that failure mode in When an Empty Mount Looked Like an Empty Library.

The lesson is simple: validate the filesystem identity, not just the pathname.

4. Application mapping and permissions

Does the application receive the correct subpath, with the permissions required for its actual job?

For containerized workloads, bind mounts add another boundary. Docker’s current documentation notes that bind mounts depend on the host filesystem path and are writable by default unless configured read-only. (Docker bind mounts)

So “the host can see it” still does not prove “the application can use it correctly.”

These four layers give me a much better troubleshooting sequence than restarting services until the symptom disappears.

The storage host needs its own health model

Once other systems depend on a storage machine, monitoring only CPU and memory misses the important questions.

The useful signals are closer to the contract:

  • Is the pool healthy?
  • Are expected devices present?
  • Did the most recent scrub complete without unresolved errors?
  • Are the expected datasets available?
  • Are exports being served?
  • Can an authorized client reach the required export?
  • Is the expected filesystem mounted on the consuming host?
  • Can the intended application identity perform its required read or write operation?
  • Are backup and replication jobs completing?
  • Has a restore been tested recently enough that I still trust the process?

That last item is the one monitoring systems cannot prove for me automatically.

A green backup job tells me that data moved somewhere.

A restore test tells me whether the recovery path actually works.

Small hardware can own a large blast radius

The storage server does not have to be the fastest machine in the rack to become the most consequential.

Once it owns authoritative data for multiple applications, maintenance on that system deserves more discipline than its hardware might suggest.

That means I care less about whether it wins a benchmark and more about whether it has:

  • Predictable storage health
  • Sufficient memory and I/O headroom for its actual role
  • Stable networking
  • Controlled updates
  • Documented exports and permissions
  • Monitoring that reflects storage health rather than mere process uptime
  • Backups outside its own failure domain
  • A rebuild and restore path I can execute without remembering hidden details

Hardware importance comes from responsibility.

A modest machine can still become a major dependency.

Separation made maintenance easier to reason about

The biggest advantage of a dedicated storage role is not performance.

It is that I can make changes to one layer without pretending they are changes to the entire environment.

I can rebuild an application host without moving the authoritative bulk data at the same time. I can change a container deployment while leaving the storage layout alone. I can investigate an NFS problem without questioning whether an application database caused it. I can test a storage recovery process without simultaneously rebuilding every service that consumes the data.

That is not fewer dependencies.

It is more visible dependencies.

I consider that an improvement.

Hidden coupling feels simple only until something fails.

What I would define before building it again

If I were creating the storage role from scratch today, I would document the contract before creating the first client mount.

For every important dataset, I would record:

  • Purpose
  • Authoritative owner
  • Expected consumers
  • Read/write requirements
  • Identity and group model
  • Export scope
  • Expected client filesystem identity
  • Application mapping requirements
  • Snapshot policy
  • Backup destination and retention intent
  • Restore procedure
  • Validation steps
  • Expected behavior during a storage outage

I would also define which details do not belong in the public version of that documentation.

Real hostnames, IP addresses, export paths, account names, internal directory layouts, credentials, keys, and environment-specific identifiers add almost nothing to the reusable lesson while creating unnecessary exposure.

The design matters more than the coordinates.

The second server changed the question

Before separating storage, I mostly asked whether a service could reach its files.

Now I ask a better set of questions.

Who owns the data? Which layer is responsible for presenting it? Which identities are allowed to change it? Which failures can the local storage survive? Which failures require another copy? What proves a restore works? What should dependent applications do when the storage is unavailable?

Those questions are less exciting than adding another service to a dashboard.

They are also the questions that make the environment easier to trust.

The secondary host did not improve my homelab because it was another computer.

It improved the homelab because it forced storage to become an explicit responsibility.

Consolidation note: This retained article now includes the distinct operational material from the earlier Field Note that covered the same reader intent.

The authoritative copy must be explicit

Shared storage becomes dangerous when several directories look like plausible sources.

A migration, temporary copy, old mount, synchronization job, and backup restore can leave behind multiple versions of the same data. Without a documented authority, a later repair may preserve the wrong copy or synchronize stale data over the correct one.

For every important dataset, I want to know:

QuestionWhy it matters
Where is the authoritative copy?Prevents two systems from accepting conflicting writes
Which applications may change it?Defines ownership and limits damage
Which copies are replicas or backups?Prevents a secondary copy from silently becoming production
How is freshness verified?Avoids restoring or syncing stale data
What is the recovery order?Ensures dependencies return in a usable sequence
What proves recovery succeeded?Replaces “the files are visible” with an actual validation result

This record does not need to expose private names publicly. Role-based labels such as storage host, application host, and backup target are enough to explain the architecture while the exact implementation remains in private documentation.

Performance belongs to the entire path

A fast pool cannot overcome every network, application, or client limit.

For media playback, the experienced result can depend on:

  • drive and pool activity;
  • network-filesystem behavior;
  • the link between storage and compute;
  • the application host’s CPU, memory, and local cache;
  • container path configuration;
  • the media format;
  • whether the client can direct-play it;
  • whether transcoding introduces another workload;
  • the final client network connection.

A slow copy or buffering stream therefore needs evidence before storage receives the blame.

I work through the path in layers:

  1. Is the pool healthy?
  2. Is the storage system under abnormal load?
  3. Is the network path stable?
  4. Is the remote mount present and responsive?
  5. Is the application reading the expected file from the expected path?
  6. Is another operation, such as transcoding, actually causing the delay?
  7. Can a controlled test reproduce the problem outside the application?

Storage performance is not experienced at the drive. It is experienced at the end of the dependency chain.

Organization is part of recovery

Directory and dataset structure should reflect different operational needs.

Application configuration, databases, original files, replaceable media, downloads, backups, and temporary data do not deserve identical retention, permissions, snapshot schedules, or recovery priority.

Combining everything because space is available makes future migrations and restores harder. It also encourages backup policies that are either wasteful or incomplete.

A useful structure makes it easier to answer:

  • What must be backed up independently?
  • What can be regenerated?
  • What needs an application-aware backup procedure?
  • What should never be writable by a particular service?
  • What can be restored later without blocking critical service recovery?

Organization does not replace documentation, but it gives the documentation a stable model to describe.

AI transparency

AI assisted with organization, technical verification, and editorial refinement. The storage architecture, ZFS/NFS relationship, permission model, failure patterns, and conclusions are based on my own homelab experience. Infrastructure identifiers and environment-specific details have been generalized or omitted.

JO

Written by

Jessie Owens

I run Eldritch IT and write about the systems, repairs, infrastructure decisions, and business lessons behind the work.