Hermes SEG splits storage into five independent tiers so each can live on the right kind of disk for its workload. Four are operator-chosen at install time; the fifth (Config) is implicit — chosen by where the operator git cloned the repo.
| Tier | Default path | Contents | Storage profile |
|---|---|---|---|
| 1. Config | install root (implicit) | Repo working tree, config/ subtrees, install script, secrets in config/hermes/opt/hermes/keys/, .env, .hermes_install_config |
Fast SSD, modest size — chosen by where the repo lives |
| 2. Data | /mnt/data (DATA_MOUNT) |
DBs (MariaDB, Authelia, OpenLDAP), Amavis runtime state, ClamAV signatures, Fangfrisch state, Lucee server home, sieve scripts, all service logs, OpenDMARC, Postfix queue | Fast SSD; sized for DB growth + log retention |
| 3. Archive | /mnt/archive (ARCHIVE_MOUNT) — added in #260 |
Amavis quarantine archive | Cheap bulk; sized for retention policy × quarantine inflow |
| 4. Vmail | /mnt/vmail (VMAIL_MOUNT) |
Dovecot mailboxes | Cheap bulk; sized for users × quota |
| 5. Nextcloud | /mnt/files (FILES_MOUNT) |
Nextcloud app + user files + Nextcloud's Redis cache | Cheap bulk; sized for user file storage |
Each tier is one host path; the install script lays out the canonical sub-directory structure underneath it.
| Tier | Why it gets its own disk |
|---|---|
| Config | Frequent reads (every container start); small footprint; lives with the install script + version control |
| Data | High write rate (logs + DBs); benefits from fast SSD; backup hot spot |
| Archive | Grows unboundedly with retention policy; cold reads (admin browses quarantine occasionally); cheaper bulk storage; backup cadence independent of Data |
| Vmail | Grows linearly with user count × quota; cheaper bulk storage; separate backup cadence (often less frequent than Data) |
| Nextcloud | Same growth characteristics as Vmail but different access pattern; often shared across multiple Hermes installs in larger deployments |
Smaller deployments can collapse tiers — point Archive, Vmail, and Nextcloud at the same path as Data for a single-disk install. Each tier is its own prompt so the operator picks per workload.
| Sub-path | Named volume | Service |
|---|---|---|
dbase/ |
db_data |
MariaDB |
authelia/db/ |
authelia_db |
Authelia state DB |
authelia/logs/ |
authelia_logs |
Authelia logs |
authelia/redis/ |
authelia_redis |
Authelia Redis |
commandbox/serverhome/ |
commandbox_serverhome |
Lucee server home |
dmarc/logs/ |
dmarc_logs |
OpenDMARC logs |
dovecot/logs/ |
dovecot_logs |
Dovecot service logs |
dovecot/sieve/ |
dovecot_sieve |
Sieve scripts (shared by commandbox + dovecot) |
ldap/data/ |
ldap_data |
OpenLDAP data |
ldap/logs/ |
ldap_logs |
OpenLDAP logs |
mail_filter/data/amavis/ |
mail_filter_data_amavis |
Amavis runtime state (PID files, scan tmp dirs — small, latency-sensitive) |
mail_filter/data/clamav/ |
mail_filter_data_clamav |
ClamAV signatures |
mail_filter/data/fangfrisch/ |
mail_filter_data_fangfrisch |
Fangfrisch state |
mail_filter/logs/ |
mail_filter_logs |
Mail filter logs |
nginx/logs/ |
nginx_logs |
Nginx logs |
openarc/logs/ |
openarc_logs |
OpenARC logs |
postfix_dkim/logs/ |
postfix_dkim_logs |
Postfix logs |
postfix_dkim/queue/ |
postfix_dkim_queue |
Postfix mail queue |
| Sub-path | Named volume | Service |
|---|---|---|
amavis/ |
amavis_data |
Amavis quarantine archive (admin-browsable, grows with retention) |
Note: the Amavis runtime state (mail_filter/data/amavis/ named volume mail_filter_data_amavis) stays on the Data tier — it's small, doesn't grow with retention, and benefits from fast SSD latency. Only the quarantine archive moved.
| Sub-path | Named volume | Service |
|---|---|---|
dovecot/ |
dovecot_mail |
Dovecot mailboxes |
| Sub-path | Named volume | Service |
|---|---|---|
app/ |
nextcloud |
NC app + user files |
redis/ |
nextcloud_redis |
NC's Redis cache |
-
prompt_mount_points()asks the operator for four paths (Data / Archive / Vmail / Nextcloud) — Config is already chosen by where the repo lives. Defaults/mnt/data,/mnt/archive,/mnt/vmail,/mnt/files. Choices saved to.hermes_install_configat the install root. -
provision_mount_dirs()creates the entire sub-directory layout under each chosen path with the correct UID/GID for the containers that will write to them. Critical: bind-mounted volumes (type: none, o: bindindocker-compose.yml) require the source directory to pre-exist — Docker refuses to start the container otherwise. -
generate_compose_override()writes the four mount-point variables (DATA_MOUNT/ARCHIVE_MOUNT/VMAIL_MOUNT/FILES_MOUNT) to.envat the install root.docker-compose.ymlreferences these variables directly in itsdevice:lines (e.g.device: ${ARCHIVE_MOUNT}/amavis) — Docker Compose substitutes at runtime. The legacydocker-compose.override.ymlapproach was retired in #179; the function name was kept for backwards-compatibility with the--generate-overrideCLI flag. -
All four mount points are required. Empty values would resolve to dangerous relative paths during
device:substitution (e.g. empty${ARCHIVE_MOUNT}/amavis→/amavisat the host root). For single-disk installs, point all four prompts at the same path.
install_hermes_docker.sh, rotate_db_credentials.sh, and any other Hermes script needing the install root use a walk-up self-locator pattern that finds docker-compose.yml by walking up from BASH_SOURCE[0]:
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -z "${HERMES_ROOT:-}" ]]; then
HERMES_ROOT="$SCRIPT_DIR"
while [[ "$HERMES_ROOT" != "/" ]] && [[ ! -f "$HERMES_ROOT/docker-compose.yml" ]]; do
HERMES_ROOT="$(dirname "$HERMES_ROOT")"
done
if [[ "$HERMES_ROOT" == "/" ]]; then
echo "ERROR: Could not locate docker-compose.yml in any parent of $SCRIPT_DIR" >&2
echo "Set HERMES_ROOT environment variable manually and retry." >&2
exit 1
fi
fiThis is depth-independent (works at 1 level or 5 levels deep in the tree) and survives the script being relocated. Do not use a hardcoded dirname/.. chain — it depends on the script's exact depth and breaks silently if the script moves.
.hermes_install_config is the source of truth for which paths the operator chose. Scripts that need this (system_backup.sh, system_restore.sh) source the file via the load_config() helper. Format:
DATA_MOUNT=/mnt/data
ARCHIVE_MOUNT=/mnt/archive
VMAIL_MOUNT=/mnt/vmail
FILES_MOUNT=/mnt/files
ENABLE_NEXTCLOUD=trueThe file lives in the Config tier (install root), so it's part of every Config-tier backup automatically.