This compose setup builds a custom hermes-agent image from the local Dockerfile rather than using a published image directly.
The local Dockerfile is used to install additional dependencies and package extensions required by your Hermes deployment.
This custom image:
- builds from the upstream
nousresearch/hermes-agentbase image - installs browser, PowerPoint, and document-processing libraries
Use this custom image when your Hermes setup requires the extra packages and runtime configuration that are not included in the upstream base image.
This image ships Leafwiki as optional supervised s6-overlay longrun services. Leafwiki provides a Markdown wiki editor/viewer that's compatible with the wiki that Hermes generates.
- Leafwiki is only enabled when
LEAFWIKI_DATA_DIRis set. If the variable is unset, both theleafwikiandleafwiki-resyncservices are disabled. - The
leafwikiservice runs/usr/local/bin/leafwikias thehermesuser. - The
leafwiki-resyncservice sendsSIGHUPto the runningleafwikiprocess at the interval configured byLEAFWIKI_RESYNC_FREQ. Leafwiki uses this signal to rescan its data directory for Markdown files that were changed outside the wiki UI, so external edits are picked up without restarting the service. LEAFWIKI_DATA_DIRsets the leafwiki data directory (for example/opt/data/leafwiki).
Hermes performs a lot of read/write activity in its home folder. At times, it can generate a great amount of bloat during its operation. In cloud environments that use network-backed storage such as Amazon EFS, this can become expensive because every I/O operation is charged and adds latency.
To reduce that cost, this image uses rclone inside a custom entrypoint.sh. Important persistent files and folders can either be copied between the network volume and the container's fast local disk, or symlinked directly to the network volume. Synced targets are cached locally while Hermes runs; a background job copies them back to the network volume, and a final copy runs on SIGTERM. Linked targets are read and written straight on the network volume and are not synced by the background job. Volatile, non-critical data never leaves the local disk and is discarded with the container.
| Variable | Required | Description |
|---|---|---|
PERSISTENT_DATA_HOME |
Yes | Root directory on the host (or network volume) where persistent data lives, e.g. /efs/hermes-data. |
PERSISTENT_TARGETS_SYNC |
No* | Comma-separated list of files and/or folders inside /opt/data that should be kept in $PERSISTENT_DATA_HOME. Append / to a name to treat it as a directory. Entries may also specify a custom sync interval using the format (target | freq), e.g. (wiki/ | 60). freq follows the same format as PERSISTENT_TARGETS_SYNC_FREQ and overrides the default for that target. Spaces around the target, |, and frequency are allowed. |
PERSISTENT_TARGETS_SYNC_EXCLUSIONS |
No | Comma-separated list of paths or patterns inside /opt/data that should be skipped when syncing directory targets from PERSISTENT_TARGETS_SYNC. Paths are translated to be relative to each directory target before being passed to rclone as --exclude filters. Append / to directory exclusions to exclude them recursively. |
PERSISTENT_TARGETS_SYNC_FREQ |
No | Default interval between background syncs from local disk back to $PERSISTENT_DATA_HOME. Plain seconds or a suffix of s, m, h, d. Defaults to 3600 (1 hour). |
PERSISTENT_TARGETS_LINK |
No* | Comma-separated list of files and/or folders inside /opt/data that should be symlinked directly into $PERSISTENT_DATA_HOME. Append / to a name to treat it as a directory. Linked targets are not copied by rclone and are not synced by the background service; Hermes reads and writes them straight on the network volume. |
ADDITIONAL_PROFILES |
No | Comma-separated list of extra Hermes profile names. For each profile <name>, targets are also synced or symlinked between $PERSISTENT_DATA_HOME/profiles/<name> and /opt/data/profiles/<name>. |
LEAFWIKI_DATA_DIR |
No | Path to the leafwiki data directory (for example /opt/data/leafwiki). When set, leafwiki and leafwiki-resync are started. When unset, both services stay disabled. |
LEAFWIKI_RESYNC_FREQ |
No | Interval at which the leafwiki-resync service sends SIGHUP to leafwiki so it rescans for Markdown files changed outside the wiki UI. Plain seconds or a suffix of s, m, h, d. Defaults to 3600 (1 hour). |
RCLONE_TRANSFERS |
No | Number of file transfers to run in parallel. Passed to rclone as --transfers. Defaults to 32. |
RCLONE_CHECKERS |
No | Number of checkers to run in parallel. Passed to rclone as --checkers. Defaults to 64. |
*At least one of PERSISTENT_TARGETS_SYNC or PERSISTENT_TARGETS_LINK must be set for the wrapper to do anything with persistent data.
- Do not set
USER hermesin the Dockerfile and do not start the container with--user. The Hermes base image is designed to start as root and drop to thehermesuser internally. - The wrapper runs as root so it can configure rclone, create directories, and
chownthe synced data to the user the Hermes runtime will run as. - If you remap the
hermesuser withHERMES_UID/HERMES_GID(or thePUID/PGIDaliases), the wrapper uses the same UID/GID for the persistent volume so ownership stays consistent. - On EFS and similar network filesystems, the container root must be allowed to create directories and change ownership. If your filesystem is configured with root squashing, either disable it for the mount or set the directory owner on the host to the expected UID/GID before starting the container.
At startup the entrypoint reads PERSISTENT_DATA_HOME, PERSISTENT_TARGETS_SYNC, PERSISTENT_TARGETS_SYNC_EXCLUSIONS, PERSISTENT_TARGETS_LINK, and PERSISTENT_TARGETS_SYNC_FREQ. It then prepares persistent data in two independent ways: link targets are symlinked straight to the network volume, and sync targets are copied between the network volume and the container's local disk.
If a target is listed in both variables, the link takes precedence and rclone ignores it.
These targets are symlinked from $PERSISTENT_DATA_HOME into /opt/data before Hermes starts.
- A trailing
/means the target is a directory. - Source files that do not exist yet get an empty placeholder on the persistent volume so the symlink is not dangling.
- When the target ends with
.db, any SQLite companion files (.db-shmand.db-wal) created at runtime are written next to the real database on the persistent volume. - The same targets are linked for each profile in
ADDITIONAL_PROFILESto/opt/data/profiles/<profile>. - Hermes reads and writes these targets directly on the network volume. They are not copied by the background service.
The entrypoint configures an rclone persistent: alias remote that points to $PERSISTENT_DATA_HOME, then copies the remaining sync targets from the persistent volume to /opt/data before Hermes starts.
- A trailing
/means the target is a directory. - When the target ends with
.db, the SQLite companion files (.db-shmand.db-wal) are synced as well. - The same targets are synced for each profile in
ADDITIONAL_PROFILESto/opt/data/profiles/<profile>. - Hermes runs with these files on local disk, so runtime I/O for them is local.
Directory targets listed in PERSISTENT_TARGETS_SYNC can be partially excluded via PERSISTENT_TARGETS_SYNC_EXCLUSIONS.
- Each exclusion is interpreted as a path relative to
/opt/data. For example, withPERSISTENT_TARGETS_SYNC: "logs/"andPERSISTENT_TARGETS_SYNC_EXCLUSIONS: "logs/temp/", thelogs/temp/directory is skipped during sync. - Exclusions are translated to be target-relative before being passed to rclone as
--excludefilters, so each directory target only receives the filters that apply to it. - Patterns that do not start with a target name are treated as target-relative filters (for example
*.tmpexcludes temporary files inside every synced directory). - Directory exclusions should end with
/so rclone recursively excludes the directory and its contents. - Exclusions only apply to directory sync targets; file targets are synced as a single path and cannot be partially excluded.
- The same exclusions are applied to each profile in
ADDITIONAL_PROFILES.
After the startup setup, the original Hermes entrypoint /opt/hermes/docker/entrypoint-dispatch.sh starts.
- A background service schedules each sync target independently and copies it from
/opt/databack topersistent:when its own interval elapses. Targets without a custom interval usePERSISTENT_TARGETS_SYNC_FREQ. - Link targets are never synced by this service.
- When the container receives
SIGTERM, the background service stops and one final sync topersistent:runs before exit. The service has unlimited finish timeout anddocker-compose.ymluses a longerstop_grace_periodso the final upload is not killed before it completes. - Checksum/hash comparisons are not used because they are expensive over network storage such as EFS; rclone compares by size and modification time instead.
services:
hermes:
build: .
environment:
PERSISTENT_DATA_HOME: /opt/data/efs
PERSISTENT_TARGETS_SYNC: "config.yaml,(state.db| 5m),logs/,(memories/| 10m)"
PERSISTENT_TARGETS_SYNC_EXCLUSIONS: "logs/temp/,memories/cache/*.tmp"
PERSISTENT_TARGETS_LINK: "SOUL.md, projects.db, wiki/"
ADDITIONAL_PROFILES: "work,home"
PERSISTENT_TARGETS_SYNC_FREQ: 1h
volumes:
- hermes-network-storage:/opt/data/efsIn this example:
SOUL.md,projects.db, andwiki/are symlinked straight to the network disk. They are read and written directly on$PERSISTENT_DATA_HOMEand are not handled by the background sync service.config.yamlandlogs/are copied from the network disk to/opt/dataat startup and synced back every hour.logs/temp/is excluded from thelogs/sync.state.db(plusstate.db-shmandstate.db-wal) is synced back every 5 minutes.memories/is copied from the network disk to/opt/dataas a directory at startup and synced back every 10 minutes.memories/cache/*.tmpis excluded from thememories/sync.- The same targets are also synced or linked for the
workandhomeprofiles under/opt/data/profiles/workand/opt/data/profiles/home. - All other Hermes read/write traffic uses the container's local disk.