Self-Hosted File Browser
Request path and storage layout. The red edge marks the NFSv4 dependency that the manifest pins explicitly.
The Challenge
Roughly 10TB of media and archive storage lives on a single NFS server. Reaching any of it meant either an SSH session or mounting the shares on every client that needed them, which is awkward from a phone, awkward from a borrowed laptop, and impossible to delegate safely.
The requirement was ordinary: browse, upload and move files from a browser, over TLS, behind authentication, without exposing the NFS server itself to anything.
The Solution
A containerised file manager runs on a k3s cluster with the NFS tree mounted as a ReadWriteMany persistent volume. Access is through an nginx reverse proxy terminating TLS on an internal hostname served by CoreDNS, so the storage server is never reachable directly. Credentials live in AWS Secrets Manager rather than in a manifest, and the whole deployment is applied through a Jenkins job from version-controlled manifests.
It was later migrated between two clusters with no DNS change at all, because the reverse proxy and the hostname had always been anchored to the host that was being migrated to.
Implementation Details
Most of the engineering in this one was in the storage layer, and very little of it was obvious from the outside.
The mount depends on a protocol version nothing declared
The volume mounts a parent directory that the NFS server does not actually export — only its subdirectories are exported. It works solely through the NFSv4 pseudo-root, which presents exported subtrees inside a synthetic namespace. Under NFSv3 the same mount is refused outright.
That mattered because this server has a documented history of stale file handles under NFSv4, and the equally documented remedy is to force clients onto NFSv3. Applying the known-good fix to this volume would not have improved it; it would have made the mount impossible. The persistent volume now pins the protocol version explicitly, with a comment addressed to whoever is about to change it.
Faults that were invisible until something moved
The migration surfaced two latent problems that had been present for months. The application database — users, shares and settings — was mounted on ephemeral storage, so every restart silently discarded it and regenerated the admin account with a new random password. And once given durable storage, the container could not write to it: the image runs as an unprivileged user while the orchestrator creates host directories owned by root, and the usual declarative remedy for volume ownership does not apply to host-path volumes. A privileged init container now takes ownership, which is idempotent and survives a node rebuild.
Running software whose upstream has stopped
The upstream project was archived with a final release and no further security fixes. Rather than migrate away from an interface that works well, the deployment was audited against the published advisories to establish which actually apply: self-signup disabled, token authentication rather than proxy headers, no shell configured, no access rules, and no internet exposure together neutralise almost all of them, including an unpatched critical. Those four settings are now documented as load-bearing, with instructions to re-read the advisories if any of them changes. A fork is planned for the remainder.