Deploying hermes as a fly.io "claw"¶
You can deploy hermes as a long-running "claw" on fly.io, reachable over a messaging gateway. These instructions assume Telegram; adapt for other messaging gateways.
Install and authenticate flyctl:
Create fly.toml:
app = "my-hermes-agent-claw"
primary_region = "iad"
[env]
TZ = "America/New_York"
# Persist the faster-whisper model cache across restarts.
# Without this, the model re-downloads (~142 MB) on every deploy.
HF_HOME = "/home/harness/.hermes-openrouter/.cache/huggingface"
[build]
image = "ghcr.io/capotej/harness:hermes-1.8.0"
[processes]
app = "hermes gateway"
[[mounts]]
source = "my_hermes_agent_claw_data"
destination = "/home/harness/.hermes-openrouter"
initial_size = "1gb"
[[vm]]
size = "shared-cpu-1x"
memory = "512mb"
[[restart]]
policy = "always"
max_retries = 3
Create the app, volume, and secrets, then deploy:
fly apps create my-hermes-agent-claw
fly volumes create my_hermes_agent_claw_data --region iad --size 1 --app my-hermes-agent-claw
fly secrets set OPENROUTER_API_KEY=<your-key> --app my-hermes-agent-claw
# https://hermes-agent.nousresearch.com/docs/user-guide/messaging/telegram#option-b-manual-configuration
fly secrets set TELEGRAM_BOT_TOKEN=<your-token> --app my-hermes-agent-claw
fly secrets set TELEGRAM_ALLOWED_USERS=<your-user-ids> --app my-hermes-agent-claw
fly secrets set GH_TOKEN=<your-personal-access-token> --app my-hermes-agent-claw
fly deploy --app my-hermes-agent-claw
GitHub CLI access: The
GH_TOKENsecret makes theghCLI available inside the container. Tell the agent to addterminal.env_passthrough: [GH_TOKEN]to itsconfig.yamlso the token is accessible in the sandbox.
Message the bot via Telegram, or wire it up to a scheduled workflow — see the daily briefing bot guide for an example.
Customizing the claw — don't extend the image¶
When you want to give the claw extra capabilities (tool wrappers around your APIs, an opinionated initial system prompt, custom gh-style scripts), the temptation is to write a Dockerfile that does FROM ghcr.io/capotej/harness:hermes-1.8.0 and bakes everything in. Don't. Two problems:
- The fly volume mounts on top of
/home/harness/.hermes-openrouter, which silently hides anything youCOPYinto that path on first boot. - Hermes treats
config.yamlas mutable state — TUI tweaks, model switches, and persona toggles are persisted viasave_config(). A derived image fights that ownership.
The supported pattern is to use the upstream image unmodified and inject your customizations via fly's [[files]] section. Files at non-volume paths get refreshed on every deploy; files seeded into /etc/harness/hermes-defaults/openrouter/ get copied into the volume on first boot only (via entrypoint-hermes.sh's cp -rn) so hermes' subsequent runtime config edits stick across restarts.
Example — add a crm API wrapper script and an initial system prompt without building a new image:
# fly.toml — append to the example above
# Tool wrappers — written to a non-volume path. Refreshed on every deploy.
# fly [[files]] preserves the local file's exec bit, so your scripts run
# as-is from the agent's sandbox.
[[files]]
guest_path = "/etc/myclaw/bin/crm"
local_path = "bin/crm"
# Initial config + persona. Upstream's hermes entrypoint copies these into
# the volume on first boot only — after that, hermes owns its config.
[[files]]
guest_path = "/etc/harness/hermes-defaults/openrouter/system-prompt.md"
local_path = "config/system-prompt.md"
To force a refresh of config.yaml or system-prompt.md from your repo after the first boot, SSH in and delete the volume's copy before redeploying:
fly ssh console --app my-hermes-agent-claw \
-C 'rm /home/harness/.hermes-openrouter/system-prompt.md'
fly deploy --app my-hermes-agent-claw
The benefits over a derived image:
- Faster deploys — no rebuild, just pull the upstream image and apply files. Seconds instead of minutes.
- Trivial upstream upgrades — bump one tag in
fly.toml. - No fight with hermes over
config.yamlownership. - One fewer artifact to maintain, sign, and verify.