Deploy Hermes Agent — the self-improving AI agent from Nous Research — as a Fly.io Machine app, so you can chat with it from Telegram, Discord, Slack, WhatsApp, or Signal no matter where you are.
Cost: roughly ~$15/month on the default config (shared-cpu-2x / 2 GB RAM / 10 GB volume, running 24/7) depending on your region. Can be scaled up or down, turned off when you're not using it. You're charged for running machine time and volume storage.
Time to first message: a few minutes, most of it waiting for the initial Docker build.
- flyctl installed and logged in: install guide, then
fly auth login - A Fly.io account with a card on file (Machines and volumes aren't in the always-free tier)
- An LLM API key. Easiest is OpenRouter — one key, hundreds of models. Anthropic, OpenAI, and Nous Portal also work.
- A Telegram bot token. Message @BotFather →
/newbot→ follow prompts → copy the123456:ABC…token. - Your Telegram user ID. Message @userinfobot → it replies with your numeric ID. You'll need this so the bot only talks to you.
(Prefer Discord, Slack, or something else? Skip the two Telegram bullets and see the Hermes messaging docs for platform-specific credential setup.)
1. Clone this repo and Hermes itself:
git clone https://github.com/fly-apps/hermes-flyio.git
cd hermes-flyio
git clone https://github.com/NousResearch/hermes-agent.git2. Pick an app name. Fly app names are globally unique, so add some randomness:
export APP="hermes-$(whoami)-$(openssl rand -hex 3)"
sed -i.bak "s/my-hermes/$APP/" fly.toml && rm fly.toml.bak
echo "App name: $APP"(If you'd rather pick by hand, edit fly.toml's app = … line and export APP=your-name so the rest of the commands work.)
3. Create the app and its volume:
fly apps create $APP
fly volumes create hermes_data --region sjc --size 10 --yes --app $APPBoth commands are idempotent enough — if fly apps create says the name is taken, re-run step 2 to pick a new one.
4. Set your secrets:
fly secrets set OPENROUTER_API_KEY=sk-or-your-key-here --app $APP
fly secrets set TELEGRAM_BOT_TOKEN=123456:ABC-your-token-here --app $APP5. Deploy:
git -C hermes-agent checkout -- Dockerfile \
&& cp fly.toml hermes-agent/ \
&& cat Dockerfile.append >> hermes-agent/Dockerfile \
&& fly deploy hermes-agent/ --app $APPGrab a coffee — the first build takes a few minutes. It's installing uv, Node, Playwright's Chromium, and every Python dependency in Hermes's [all] extra. Subsequent deploys are much faster thanks to layer caching.
You'll know it worked when fly logs shows:
⚕ Hermes Gateway Starting...
followed by a warning about no user allowlists — that's expected; you haven't run setup yet.
No public IP by default. The shipped
fly.tomldoesn't expose any ports — Telegram, Discord, Slack, and friends are all outbound-only, so the machine talks out to the platform APIs and doesn't need to be reachable from the internet. There's nothing to browse athttps://<your-app>.fly.dev; that's intentional. See Webhooks further down if you do want inbound HTTP.
Run Hermes's interactive setup wizard over SSH. It walks through provider + model, personality, optional tool API keys, and messaging platform config in one flow:
fly ssh console --pty --app $APP -C "hermes setup"- Pick Quick Setup
- When it asks "Which tools would you like to configure?" (Exa, Firecrawl, Browserbase, FAL, ElevenLabs, Tinker, …)
- Just hit
ENTERwith nothing selected — they're all optional
- Just hit
- Select Telegram as the messaging platform
- Paste the bot token when prompted (should already be in env — it should offer to reuse it)
- When it asks for allowed users, paste the numeric Telegram ID you got from @userinfobot
Restart the gateway so it picks up the new config:
Note: At the end of setup, Hermes asks "Do you want to restart the gateway?" — you can say yes, but you still need to run
fly apps restartbelow. That prompt restarts the gateway process inside the SSH session, which ends when your SSH session closes.fly apps restartrestarts the actual Machine so the gateway comes up fresh with your new config.
fly apps restart $APPFinally, find your bot on Telegram (search for the username you gave @BotFather), send /start, and chat.
fly logs --app $APP # tail the gateway while you testIf SSH fails with "connection refused" or similar: the gateway is crash-looping. Run
fly logs --app $APPfirst to see the error — don't retry SSH blindly.
git -C hermes-agent pull
git -C hermes-agent checkout -- Dockerfile \
&& cp fly.toml hermes-agent/ \
&& cat Dockerfile.append >> hermes-agent/Dockerfile \
&& fly deploy hermes-agent/ --app $APPYour config, memories, skills, and session history live on the volume at /opt/data and roll forward untouched. Bundled skills are reconciled on boot without overwriting your own edits.
Everything Hermes learns — memories, skills, SOUL.md, session DB, cron jobs — lives on the hermes_data volume. Fly automatically takes daily snapshots of every volume and keeps them for 5 days by default, so you don't need to schedule anything yourself. Retention can be set anywhere from 1 to 60 days.
To bump retention on your existing volume:
fly volumes list --app $APP
fly volumes update <volume-id> --snapshot-retention 30 --app $APPOr bake it into fly.toml under [mounts] so every new volume picks up the same setting:
[mounts]
source = "hermes_data"
destination = "/opt/data"
snapshot_retention = 30To see what snapshots exist, or restore one into a new volume:
fly volumes snapshots list <volume-id> --app $APP
fly volumes create hermes_data_restored \
--snapshot-id <snapshot-id> --size 10 --app $APPSnapshot storage is free for the first 10 GB, then $0.08/GB/month on the incremental stored size (not the full volume). A Hermes deployment usually sits well inside the free tier at the default retention — worth a glance at pricing if you bump retention way up or run a very active volume. See the volume snapshots docs for the full details.
One important caveat: snapshots are daily, so anything Hermes learned between the last snapshot and a host failure is lost. For extra safety against that window, pull a local archive with Hermes's own backup command:
fly ssh console --pty --app $APP -C "hermes backup create /opt/data/backup.tar.gz"
fly sftp get /opt/data/backup.tar.gz --app $APPMost of the time you'll chat with Hermes through whichever messaging platform you set up — Telegram, Discord, Slack, WhatsApp, Signal. That's the whole point of running the gateway: your agent is reachable from your phone, your desktop, wherever.
But you can also drop into Hermes's native terminal UI directly on the machine — useful for ad-hoc debugging, running hermes doctor, or just grabbing a quick session when you don't want to pull out your phone:
fly ssh console --pty --app $APP -C "hermes"Exit with /quit. The TUI and the gateway share the same /opt/data volume, so memories, skills, and session history are unified — a conversation you start over SSH shows up in your history the same as one you had over Telegram.
shared-cpu-2x / 2 GB is fine for one person and a few platforms. Heavy skill execution, Playwright browsing, or RL work wants more:
fly scale vm shared-cpu-4x --memory 4096 --app $APP
fly volumes extend <volume-id> --size 20 --app $APPThe default fly.toml keeps the machine running 24/7 (min_machines_running = 1, auto_stop_machines = "off") because the gateway needs to be online to catch incoming messages. If you're on a pure webhook-only setup and want the machine to suspend when idle, set auto_stop_machines = "suspend" and drop min_machines_running.
The default config has no public ingress — no HTTP service, no allocated IPs. Messaging platforms don't need it, so nothing listens on the public hostname.
If you do want to receive inbound webhooks (GitHub, Stripe, Telegram webhook mode, custom integrations), opt in:
1. Uncomment the [http_service] block in fly.toml, and add this under [env]:
[env]
HERMES_HOME = "/opt/data"
WEBHOOK_HOST = "0.0.0.0"
WEBHOOK_PORT = "8644"2. Enable the webhook platform in Hermes so port 8644 actually has a listener:
fly ssh console --pty --app $APP -C "hermes gateway setup"
# select "webhook" in addition to your other platforms3. Redeploy and allocate IPs:
git -C hermes-agent checkout -- Dockerfile \
&& cp fly.toml hermes-agent/ \
&& cat Dockerfile.append >> hermes-agent/Dockerfile \
&& fly deploy hermes-agent/ --app $APP
fly ips allocate-v6 --app $APP
fly ips allocate-v4 --shared --app $APPExternal services can now POST to https://<your-app-name>.fly.dev/webhooks/<route-name>. Define routes with hermes webhook subscribe … or statically in config.yaml under platforms.webhook.extra.routes. See the webhook docs for route syntax and HMAC validation.
Secrets always via fly secrets set (never committed). Non-secret tunables live in fly.toml under [env].
| Variable | Set via | Purpose |
|---|---|---|
OPENROUTER_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY / NOUS_API_KEY / … |
fly secrets set |
LLM provider credentials (pick whichever you use) |
TELEGRAM_BOT_TOKEN |
fly secrets set |
Telegram bot token |
DISCORD_TOKEN |
fly secrets set |
Discord bot token |
SLACK_BOT_TOKEN / SLACK_APP_TOKEN |
fly secrets set |
Slack Socket Mode credentials |
WEBHOOK_SECRET |
fly secrets set |
HMAC secret for inbound webhooks (only if webhook adapter enabled) |
HERMES_HOME |
fly.toml |
Pinned to /opt/data so Hermes writes onto the volume |
WEBHOOK_HOST / WEBHOOK_PORT |
fly.toml |
Only set if you uncomment [http_service] for inbound webhooks |
Full list: Hermes environment variables reference.
fly deploy hermes-agent/ builds upstream's Dockerfile with the cloned Hermes repo as the build context. Before deploying, fly.toml and a short Dockerfile.append are copied in. The append does two things:
- Adds a
/usr/local/bin/hermesshim that drops to thehermesuser viagosuand invokes the venv'shermesbinary — sofly ssh console -C "hermes ..."works without needing to activate the venv or know about the internal user. - Sets
CMD ["gateway", "run"]so the machine boots straight into the messaging gateway in foreground mode. (gateway startis for installing a systemd/launchd unit on a host and fails inside a container.)
Everything else is upstream:
- Entrypoint (
docker/entrypoint.sh) runs as root, chowns/opt/data(Fly volumes mount as root), drops to thehermesuser viagosu, seeds.env,config.yaml, andSOUL.mdfrom the in-image examples, syncs bundled skills, thenexecshermes gateway run. - Volume
/opt/dataisHERMES_HOME— config, memories, skills, session DB, cron jobs, hooks, plans, workspace, and per-profile$HOMEfor spawned tools. - Playwright's Chromium is installed at
/opt/hermes/.playwrightduring build so the volume overlay doesn't hide it at runtime.
For anything past this Fly deployment, see the Hermes docs.