File system layout for your SaaS web application

This post outlines the filesystem structure I use for my SaaS web applications. The design is guided by three primary objectives:

  1. The ability to atomically revert to a previous version of the application.
  2. Support for running multiple instances with their own configurations while sharing the same application code.
  3. Adherence to the Filesystem Hierarchy Standard for optimal tool interoperability.

Never overwrite files

Ensuring a reliable way to revert to a working version is crucial — mistakes happen, and unexpected issues arise. However, the real problem starts when you lack a dependable method to recover from those issues quickly.

To address this, all application files, including executables and configurations, are deployed in an append-only manner. For each new version, a new directory is created containing all the necessary files, and a symbolic link is updated to point to this directory. The atomic nature of symbolic link updates typically ensures that switching versions is seamless and quick.

My SaaS applications are designed with zero-downtime deployments in mind. This allows me to deploy new versions by simply copying the new files to the server, updating the symbolic link, and restarting the process. As a result, the application state must always be compatible with the new version, maintaining consistency across deployments.

NodeJS runtime

Description FS Location
Installations /opt/nodejs/vXX.YY/
Currently active installation (symlinked) /opt/nodejs/current/, /usr/local/bin/

Nginx

Description FS Location
Configuration /etc/nginx/
App-specific configuration /etc/nginx/sites-available/<appname>/releases/<timestamp>/
Currently active app configuration (symlinked) /etc/nginx/conf.d/<appname>.conf

SystemD

The process management uses SystemD's Unit Templates. One parameterised unit file describes multiple instances. It is a convenient built-in way of managing multiple instances of the same application.

Description FS Location
Application units /etc/local/<appname>/releases/<timestamp>/
Currently active units (symlinked) /etc/local/<appname>/current/, /etc/systemd/system/

Configuration

Description FS Location
Application configuration /etc/local/<appname>/<instance>/releases/<timestamp>/
Currently active app configuration (symlinked) /etc/local/<appname>/<instance>/current/

Application executables

Description FS Location
Scripts, executables /usr/local/lib/<appname>/releases/<timestamp>/
Currently active executables (symlinked) /usr/local/lib/<appname>/current

Static assets

Description FS Location
Static assets /srv/www/<appname>/releases/<timestamp>/
Currently active assets (symlinked) /srv/www/<appname>/public/

State

Description FS Location
Mutable app state like SQLite databases /var/local/lib/<appname>/<instance>/
Mutable app state backups /var/local/backups/<appname>/<instance>/

Logs

Description FS Location
Nginx logs /var/log/nginx/
Application logs stdout

Summary

If your application doesn’t require support for multiple instances, you can simplify the structure by omitting the extra directory hierarchy.

You're welcome to use this layout as-is, adapt parts of it, or make your own modifications. If you have any feedback or suggestions, feel free to tell me and let me know what you think!

Back