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:
- The ability to atomically revert to a previous version of the application.
- Support for running multiple instances with their own configurations while sharing the same application code.
- 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!