afero
A drop-in filesystem abstraction for Go that swaps OS, in-memory, archive, and cloud backends without touching your code.
Repository Health
Technical Analysis
Afero is a filesystem abstraction library for Go built around a single afero.Fs interface that mirrors the standard os package. Instead of calling os.* functions directly, code accepts an afero.Fs and works unchanged whether that filesystem is the real disk, an in-memory store, a ZIP or TAR archive, or a remote backend like SFTP or Google Cloud Storage.
Beyond swapping storage backends, Afero’s composition wrappers — CopyOnWriteFs, CacheOnReadFs, BasePathFs, ReadOnlyFs, RegexpFs — let you layer filesystems into sandboxes, read-through caches, chroot jails, and read-only views without writing new storage code. Combined with io/fs interoperability and an HTTP file-server adapter, it gives Go applications a single testable, composable way to talk to any filesystem-shaped backend.
What You Get
- afero.Fs interface - a single interface mirroring os.* methods (Create, Open, Mkdir, Remove, Stat, Chmod, …) that any backend implements.
- MemMapFs - a concurrent-safe, in-memory filesystem for fast, disk-free unit tests.
- Composition wrappers - CopyOnWriteFs, CacheOnReadFs, BasePathFs, ReadOnlyFs, and RegexpFs for layering sandboxing, caching, chroot jails, and read-only or filtered views on top of any Fs.
- Archive and network backends - zipfs and tarfs read ZIP/TAR archives as filesystems, with experimental sftpfs and gcsfs backends for SFTP and Google Cloud Storage.
- io/fs and net/http bridges - IOFS/FromIOFS convert between afero.Fs and the standard library’s io/fs.FS, and HttpFs adapts any Fs for http.FileServer.
Common Use Cases
- Unit testing file-touching code - swap OsFs for MemMapFs in tests to avoid disk I/O and cleanup entirely.
- Sandboxed writes over a read-only base - use CopyOnWriteFs to let untrusted code “modify” files while the real base filesystem stays untouched.
- Caching a slow remote filesystem - wrap SFTP or GCS storage with CacheOnReadFs so repeated reads hit an in-memory layer instead of the network.
- Processing uploaded archives without extraction - open a ZIP or TAR with zipfs/tarfs and reuse the same file-reading code paths as a normal directory tree.
- Restricting plugin or user code to one directory - wrap the OS filesystem in BasePathFs to enforce a chroot-style jail.
Under The Hood
Architecture
afero.go defines the core Fs interface (Create, Mkdir, Open, OpenFile, Remove, RemoveAll, Rename, Stat, Name, Chmod, Chtimes, Chown) and a File interface mirroring os.File. Backends implement Fs directly: OsFs (os.go) delegates to the native os package, and MemMapFs (memmap.go) keeps an in-memory map of file data guarded by a sync.RWMutex for concurrent safety. Composition wrappers wrap other Fs instances rather than storage: CopyOnWriteFs (copyOnWriteFs.go) holds a base and overlay Fs and routes writes to the overlay via unionFile.go’s UnionFile; CacheOnReadFs layers a cache Fs in front of a slower base with a TTL; BasePathFs restricts paths to a subdirectory; ReadOnlyFs rejects mutations; RegexpFs filters visible files by pattern. Additional backends live in subpackages — mem/ (the in-memory file-data structure), zipfs/, tarfs/, sftpfs/, and gcsfs/ — all implementing the same Fs interface, plus internal/ for shared helpers. httpFs.go adapts any Fs to http.FileServer, and iofs.go bridges to and from the standard library’s io/fs.FS. Because everything hangs off one interface, swapping OsFs for MemMapFs at the injection site changes the storage backend for an entire call graph.
Tech Stack
Pure Go, module github.com/spf13/afero, requiring Go 1.25 per go.mod. The core package has a single direct dependency, golang.org/x/text, used in util.go for byte-order-mark stripping and rune transforms. Backend subpackages such as sftpfs and gcsfs pull in their own additional dependencies (an SFTP client, a GCS client) scoped to their own go.mod-adjacent subpackage, keeping the core dependency-free. CI runs through GitHub Actions (ci.yaml) plus an OpenSSF Scorecard workflow, alongside a legacy AppVeyor config; there’s no runtime framework since it’s a pure library, imported and called directly.
Code Quality
Fifteen _test.go files at the repo root cover the core Fs implementations, composition wrappers, and utility functions, using the standard library testing package rather than a third-party assertion framework. Errors are returned as ordinary Go error values matching stdlib os. semantics rather than panicking, preserving drop-in compatibility with code written against the os package. A .golangci.yaml config drives golangci-lint in CI on every push and pull request. The Fs interface predates Go generics and remains intentionally simple rather than parameterized.
API Design The public API deliberately mirrors the os package — NewOsFs(), NewMemMapFs(), and package-level helpers in ioutil.go/util.go (ReadFile, WriteFile, Exists, DirExists) match os and io/ioutil naming closely, so adopting Afero is close to accepting an interface argument plus one constructor call at the composition root. Documentation is a strength for a library this size: package-level GoDoc comments on every exported type, and a README with a backend-status table spanning official, experimental, and third-party implementations. The main friction is that Fs itself is a fairly large interface, raising the bar for anyone implementing a wholly new backend from scratch, though the composition wrappers make combining existing backends much lighter than writing one. Getting started requires minimal boilerplate: accept afero.Fs in a function signature and call afero.NewOsFs() at the call site.