The Humble Gatekeeper: On the Necessity of the Read-Only Filesystem

We build our small services to be resilient. We wrap them in supervisors, we log their every whisper, and we back up their precious state. We trust these measures to guard against the chaos of a crashing process or a full disk. But what of the chaos that comes from within? What of the service that, through some bug or exploit, decides to turn its own hand against its home?

This is where our most unassuming guardian stands watch: the read-only filesystem. It is the ultimate expression of the principle of least privilege, applied not to a user or a process, but to the very ground upon which the application walks. It is a declaration: "You may look, you may compute, but you may not change the foundation." In an age where we constantly fret over what might get in, we often neglect to plan for what might get out.

Consider a simple API service. Its code is deployed, its dependencies are installed. What does it truly need to write to its own directory? Perhaps a temporary upload folder or a cache, but these can be explicitly mounted elsewhere. The core application, the libraries it uses, the configuration that defines it—these are canon. They should be immutable. By mounting the application's root filesystem as read-only, we build a wall between the operational logic of the service and the volatile state it manages. A misbehaving process cannot overwrite its own binary, cannot inject malicious code into a library, cannot deface its static assets.

The Quiet Discipline of Constraint

Enforcing read-only is a form of discipline that pays dividends in clarity. It forces a conscious and explicit design for statefulness. Suddenly, you must ask: where does my application *need* to write? The answer is almost always to a dedicated data directory, a temporary volume, or a network store. This separation is a boon for operations. Backups become simpler—you know the volatile data lives in one specific place. Deployment becomes safer—you can be confident that an update will cleanly overwrite the entire application base without fear of legacy cruft hiding in a shadowed corner.

Implementing this is a quiet, boring triumph. It’s a single flag in a container runtime (`--read-only`), an option in a mount command (`ro`), or a configuration in an orchestration manifest. It is not glamorous. It will never send an alert or show up in a dashboard. Its success is measured in the absence of catastrophe. It is the gatekeeper that never sleeps, never doubts, and never allows a single byte of change to the sacred text of your application. In the relentless pulse of our services, it is the one thing that does not move, and in that stillness, we find a profound and reliable safety.

Notes & further reading

A few pages I came back to while writing this: