Private, self-hosted web UI for Hermes Agent (unofficial). Single-user AI agent, local with Ollama.
716
A private web UI for Hermes Agent that runs entirely on your own machine. Chat with an agent that can use tools, work with your files and remember you, powered by local models through Ollama. There's no cloud account, no telemetry, and nothing leaves your network unless you choose to.
Unofficial community project, not affiliated with or endorsed by Nous Research. Hermes Agent is Nous Research's open-source agent; this image is a web front end for it.
Built for one person: a personal agent you run at home or on your own server, installable as an app on desktop and mobile.
/workspace that you and the agent both use. Upload, preview (text, Markdown, images, PDF, audio/video), rename, delete, and attach files to messages./ in the message box to pick a skillSOUL.md, and share chosen profile details with its memory.127.0.0.1 unless you choose otherwise, and Hermes and Ollama aren't published at all. For access from your other devices, the recommended setup is HTTPS over your own Tailscale network, with Caddy and a real certificate, and nothing exposed to the internet.no-new-privileges, non-root user..env.docker compose up -d
docker compose exec ollama ollama pull qwen3.5:4b # the model named in OLLAMA_MODEL
https://….ts.net address; see Remote access).docker-compose.yml
# Hermes Agent WebUI: private, single-user personal agent stack
#
# Ollama (local models) → Hermes Agent (agent, tools, memory) → Hermes Agent WebUI (browser UI)
#
# Everything runs on your machine. Hermes and Ollama are never published; the web UI is published on
# 127.0.0.1 by default, and the optional Caddy service serves it over HTTPS on your Tailscale tailnet.
# Quick start:
# cp .env.example .env # then fill in the two secrets (see comments in the file)
# docker compose up -d
# docker compose exec ollama ollama pull qwen3.5:4b # or the model you set as OLLAMA_MODEL
# open http://localhost:8080 and create your account
# Remote access (recommended): set COMPOSE_PROFILES=caddy, TS_DOMAIN and TAILSCALE_IP in .env, see below.
name: hermes
services:
# ── Local model server ────────────────────────────────────────────────────────
ollama:
image: ollama/ollama:latest
restart: unless-stopped
volumes:
- ollama:/root/.ollama
networks: [hermes]
# NVIDIA GPU (needs the NVIDIA Container Toolkit): uncomment
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
# ── One-time setup: shared workspace permissions and first-run Hermes defaults ─
# The defaults are applied once (marker file in Hermes's data volume), so changes you make
# later, e.g. on the web UI's Models page, are never overwritten.
setup:
image: nousresearch/hermes-agent:v2026.8.16
entrypoint: ["/bin/sh", "-c"]
command:
- |
set -e
chmod 0777 /workspace && mkdir -p /workspace/uploads /workspace/outputs && chmod 0777 /workspace/uploads /workspace/outputs
h=/opt/hermes/bin/hermes
# Browsing: with browser.backend unset, Hermes picks the Browser Use CLI, which needs an
# already-running Chrome or a CDP URL and fails in a headless container. Use Hermes's built-in
# browser tools (bundled headless Chromium) unless you chose a backend yourself. Runs on every
# start, so existing deployments pick it up too.
backend=$$($$h config get browser.backend 2>/dev/null || true)
if [ -z "$$backend" ]; then $$h config set browser.backend off >/dev/null && echo "Browser: built-in headless Chromium (browser.backend=off)"; fi
marker=/opt/data/.hermes-webui-defaults
if [ -f "$$marker" ]; then echo "Hermes defaults already applied; nothing to do."; exit 0; fi
unset_or() { $$h config get "$$1" >/dev/null 2>&1 || $$h config set "$$1" "$$2" >/dev/null; }
# Model: the bundled (or your own) Ollama, unless a provider is already configured.
unset_or model.provider custom
unset_or model.base_url "$${OLLAMA_BASE_URL}"
unset_or model.default "$${OLLAMA_MODEL}"
unset_or model.api_mode chat_completions
# Hermes works in the shared workspace, so its files appear in the web UI's file manager.
$$h config set terminal.cwd /workspace >/dev/null
# Privacy: background jobs (titles, compression, web extraction…) use your local model
# and never fall back to cloud providers.
for task in vision web_extract compression skills_hub approval mcp title_generation triage_specifier kanban_decomposer profile_describer curator; do
$$h config set "auxiliary.$$task.provider" main >/dev/null
done
touch "$$marker"
echo "Hermes defaults applied: model=$$($$h config get model.default) via $$($$h config get model.base_url), cwd=/workspace, background tasks=main"
environment:
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434/v1}
OLLAMA_MODEL: ${OLLAMA_MODEL:-qwen3.5:4b}
volumes:
- hermes-data:/opt/data
- workspace:/workspace
restart: "no"
networks: [hermes]
# ── The agent ─────────────────────────────────────────────────────────────────
hermes:
image: nousresearch/hermes-agent:v2026.8.16
command: gateway run
restart: unless-stopped
shm_size: 1gb # headless Chromium for the browser tools; Docker's 64 MB default can crash on heavy pages
environment:
API_SERVER_ENABLED: "true"
API_SERVER_HOST: 0.0.0.0
API_SERVER_PORT: "8642"
API_SERVER_KEY: ${HERMES_API_KEY:?Set HERMES_API_KEY in .env}
API_SERVER_MODEL_NAME: ${OLLAMA_MODEL:-qwen3.5:4b}
HERMES_TIMEZONE: ${HERMES_TIMEZONE:-} # clock and scheduled tasks; UTC when empty
# Hermes's file tools may write in its data dir and the shared workspace (reports, outputs).
# The image default (/opt/data only) makes it fall back to the terminal for workspace files.
HERMES_WRITE_SAFE_ROOT: /opt/data:/workspace
volumes:
- hermes-data:/opt/data
- workspace:/workspace
depends_on:
setup:
condition: service_completed_successfully
ollama:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8642/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks: [hermes]
# ── The web UI ────────────────────────────────────────────────────────────────
hermes-webui:
image: ${HERMES_WEBUI_IMAGE:-emmaylots/hermes-agent-webui:latest}
restart: unless-stopped
ports:
- "${WEB_BIND:-127.0.0.1}:${WEB_PORT:-8080}:8080" # loopback by default; use the caddy profile for remote HTTPS
environment:
APP_NAME: ${APP_NAME:-Hermes}
HERMES_GATEWAY_URL: http://hermes:8642
HERMES_API_KEY: ${HERMES_API_KEY:?Set HERMES_API_KEY in .env}
HERMES_WEB_SECRET: ${HERMES_WEB_SECRET:?Set HERMES_WEB_SECRET in .env}
HERMES_COOKIE_SECURE: ${HERMES_COOKIE_SECURE:-false} # true when served over HTTPS
HERMES_TIMEZONE: ${HERMES_TIMEZONE:-} # must match Hermes's, so scheduled times line up
volumes:
- webui-data:/data
- workspace:/workspace
read_only: true
tmpfs: ["/tmp"]
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
depends_on:
hermes:
condition: service_healthy
networks: [hermes]
# ── Optional: HTTPS on your tailnet with Caddy + Tailscale certificates ──────────
# Enable with COMPOSE_PROFILES=caddy. Needs Tailscale on the host with MagicDNS and
# "HTTPS Certificates" turned on in the Tailscale admin console (DNS page). Caddy obtains the
# *.ts.net certificate from the host's tailscaled through its socket, so no port 80 or public
# DNS is needed, and binding to the Tailscale IP keeps the UI reachable only from your tailnet.
caddy:
image: caddy:2
profiles: [caddy]
restart: unless-stopped
ports:
- "${TAILSCALE_IP:-127.0.0.1}:443:443"
volumes:
- /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock
- caddy-data:/data
configs:
- source: caddyfile
target: /etc/caddy/Caddyfile
depends_on: [hermes-webui]
networks: [hermes]
configs:
caddyfile:
content: |
${TS_DOMAIN:-hermes.example.ts.net} {
encode zstd gzip
header {
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options nosniff
Referrer-Policy no-referrer
-Server
}
reverse_proxy hermes-webui:8080
}
networks:
hermes:
volumes:
caddy-data:
ollama:
hermes-data:
webui-data:
workspace:
.env
# Copy to .env and fill in the two secrets. Never commit .env.
# Key the web UI uses to talk to Hermes (any long random string). Generate one with:
# openssl rand -hex 32
HERMES_API_KEY=
# Encrypts stored secrets (two-factor keys). Generate with `openssl rand -hex 32` and keep it stable.
HERMES_WEB_SECRET=
# Local model served by Ollama (pull it once: docker compose exec ollama ollama pull <model>).
# Any tool-calling model works; larger models give better agent results.
OLLAMA_MODEL=qwen3.5:4b
# Use an Ollama that is already running elsewhere instead of the bundled one:
# OLLAMA_BASE_URL=http://192.168.1.10:11434/v1
# Your time zone (IANA name). Hermes uses it for its clock and for scheduled tasks; without it,
# Hermes runs on UTC and the web UI converts the times you pick.
# HERMES_TIMEZONE=Africa/Johannesburg
# Web UI address and port on this machine (127.0.0.1 = this machine only) and display name.
# WEB_BIND=127.0.0.1
# WEB_PORT=8080
# APP_NAME=Hermes
# ── Recommended remote access: HTTPS on your Tailscale tailnet via Caddy ──────────
# Needs Tailscale on the host with MagicDNS and HTTPS Certificates enabled (admin console → DNS).
# COMPOSE_PROFILES=caddy
# TS_DOMAIN=myserver.tail1234.ts.net # this machine's name: tailscale status --self
# TAILSCALE_IP=100.x.y.z # this machine's Tailscale IP: tailscale ip -4
# HERMES_COOKIE_SECURE=true # required when served over HTTPS
Generate the secrets with openssl rand -hex 32.
Set OLLAMA_BASE_URL=http://<host>:11434/v1 in .env. You can then remove the ollama service and its depends_on entry.
Hermes is an agent: it calls tools, so pick a tool-calling model. Small models (3–9B) work for everyday tasks, and larger ones are noticeably better at multi-step work. You can switch later on the Models page, or per conversation from the model picker.
| Variable | Required | Description |
|---|---|---|
HERMES_API_KEY | yes | Key for the Hermes API server; must equal Hermes's API_SERVER_KEY (the sample wires both from .env) |
HERMES_WEB_SECRET | yes | Encrypts stored secrets; keep it stable |
HERMES_GATEWAY_URL | no | Hermes API server URL (default http://hermes:8642) |
HERMES_COOKIE_SECURE | no | true when served over HTTPS |
APP_NAME | no | Name shown in the UI (default Hermes) |
HERMES_UPLOAD_MAX_BYTES | no | Upload limit (default 50 MB) |
HERMES_TIMEZONE | no | Your IANA time zone (e.g. Africa/Johannesburg). Give Hermes the same value (the sample wires both from .env). Without it, Hermes runs on UTC and the web UI converts the times you pick for scheduled tasks |
Volumes: /data holds the web UI's accounts, projects and settings; /workspace is the folder shared with Hermes.
Reach your agent from your laptop and phone over your own Tailscale network, with a real HTTPS certificate and without opening anything to the internet. The sample Compose file includes an optional caddy service. Caddy gets a trusted *.ts.net certificate from the Tailscale daemon on the host, so no port 80, public DNS or port forwarding is needed.
.env:
COMPOSE_PROFILES=caddy
TS_DOMAIN=myserver.tail1234.ts.net # from: tailscale status --self
TAILSCALE_IP=100.x.y.z # from: tailscale ip -4
HERMES_COOKIE_SECURE=true
docker compose up -d, then open https://myserver.tail1234.ts.net from any device on your tailnet.Caddy listens only on the Tailscale IP, so the UI isn't reachable from your LAN or the internet. It also adds HSTS, nosniff and no-referrer headers. The web UI's own port stays on 127.0.0.1 for local use; you can change it with WEB_BIND.
If you prefer another reverse proxy (Traefik, nginx, Cloudflare Tunnel), point it at hermes-webui:8080 and set HERMES_COOKIE_SECURE=true when serving over HTTPS.
docker compose pull && docker compose up -d
Back up the hermes-data, webui-data and workspace volumes. Hermes keeps a backup of anything the web UI changes: SOUL.md, memory and skills in /workspace/backups, and config inside Hermes's data volume.
browser.backend: off when it's unset, because Hermes otherwise defaults to its Browser Use CLI mode, which needs an already-running Chrome or browser.cdp_url and fails in a plain container. If you use Browser Use (a cloud browser or your own Chrome via CDP), set browser.backend yourself and the setup step leaves it alone.linux/amd64 and linux/arm64 (Intel/AMD PCs and servers, Raspberry Pi 4/5, Apple Silicon, ARM cloud servers). Docker picks the right one automatically, and the whole sample stack (Ollama, Hermes, Caddy) supports both.hermes model in the Hermes container, followed by a restart.HERMES_WRITE_SAFE_ROOT: /opt/data:/workspace, so Hermes's file tools can save reports in the workspace. Add it to existing deployments.HERMES_TIMEZONE setting for Hermes and the web UI.browser.backend off) and gets 1 GB of shared memory, so web browsing works in Docker.MIT. The license text ships in the image at /app/LICENSE. Hermes Agent, Ollama and Caddy are separate projects under their own licenses.
Content type
Image
Digest
sha256:a733991ce…
Size
76.3 MB
Last updated
20 days ago
docker pull emmaylots/hermes-agent-webui