Production-grade Linux Filesystem Hierarchy Standard (FHS) layout for AI Agent, automation, API, bot, and web application projects.
This repository contains a reusable Hermes Agent skill plus a hardened setup script for organizing Linux projects into clear, secure, and maintainable paths.
Many small projects start with everything in one folder:
project/
├── app.py
├── config.yaml
├── database.sqlite
├── logs.txt
└── uploads/
That breaks down quickly in production.
Problems:
- Code and runtime data get mixed.
- Secrets can accidentally be committed.
- Logs fill the wrong directory.
- Deployments can overwrite databases or user uploads.
- Permissions become messy and people start running apps as root.
- Agents cannot reliably know where to read or write files.
This skill fixes that by separating code, state, logs, and config using Linux FHS conventions.
After using this standard, every project has:
- Safer deployment because code and runtime data are separated.
- Better security because state and logs are owner-only, and config is not world-readable.
- Cleaner backups because
/var/lib/<project>contains state and/etc/<project>contains config. - Easier debugging because logs always live under
/var/log/<project>. - Repeatable onboarding because every AI/web project follows the same layout.
/var/www/<project-name>/
├── agents/
├── schemas/
└── scripts/
/var/lib/<project-name>/
├── data/
├── leads/
└── artifacts/
/var/log/<project-name>/
└── app.jsonl
/etc/<project-name>/
├── config.yaml
├── products.yaml
└── objections.yaml
| Path | Purpose | Writable By App? | Examples |
|---|---|---|---|
/var/www/<project> |
Static source code | No / rarely | agents, schemas, app code, scripts |
/var/lib/<project> |
Runtime state and data | Yes | SQLite DB, lead files, artifacts, cache |
/var/log/<project> |
Logs and telemetry | Yes | app.jsonl, error.log, access.log |
/etc/<project> |
Config and secrets | Read only by app | config.yaml, .env, product config |
hermes skills install https://github.com/wiwidcok/linux-fhs-project-layoutOr clone manually:
git clone https://github.com/wiwidcok/linux-fhs-project-layout.gitchmod +x scripts/setup_fhs.sh
./scripts/setup_fhs.sh my-projectDry run first:
./scripts/setup_fhs.sh my-project --dry-runIf you run as root, pass a non-root deployment owner explicitly:
DEPLOY_USER=ubuntu ./scripts/setup_fhs.sh my-project- Linux server or VPS
- Bash
sudo- Core utilities:
mkdir,chmod,chown,stat - Tested style: Debian/Ubuntu-like systems
This repository intentionally uses:
/var/www/<project> 755
/var/lib/<project> 700
/var/log/<project> 700
/etc/<project> 750
Why /etc/<project> is 750, not 755:
- Config directories often contain API keys,
.env, tokens, database URLs, and business knowledge. 755would make config files discoverable by all users on the server.750allows owner/group access while blocking world-read.
Bad:
/var/www/my-app/database.sqlite
Good:
/var/lib/my-app/data/database.sqlite
Bad:
/var/www/my-app/logs/app.log
Good:
/var/log/my-app/app.log
Bad:
/var/www/my-app/.env
Good:
/etc/my-app/.env
PROJECT_NAME="my-project"
stat -c '%U:%G %a %n' \
/var/www/${PROJECT_NAME} \
/var/lib/${PROJECT_NAME} \
/var/log/${PROJECT_NAME} \
/etc/${PROJECT_NAME}
test -d /var/www/${PROJECT_NAME}/agents
test -d /var/www/${PROJECT_NAME}/schemas
test -d /var/www/${PROJECT_NAME}/scripts
test -d /var/lib/${PROJECT_NAME}/data
test -d /var/lib/${PROJECT_NAME}/leads
test -d /var/lib/${PROJECT_NAME}/artifactsLinux FHS, Filesystem Hierarchy Standard, directory structure, production project layout, server folder best practice, /var/www, /var/lib, /var/log, /etc, AI agent deployment, Hermes Agent skill, web application deployment, security hardening, chmod, chown, least privilege, immutable code, mutable state.
MIT License. See LICENSE.