--- title: Hermes Docker on Kraken created: '2026-05-23' updated: '2026-06-04' type: tech namespace: personal tags: [hermes, docker, kraken, infra, how-to] related: - "[[tech/kraken-network]]" - "[[concepts/hermes-deployment-patterns]]" - "[[tech/hermes-kraken-api]]" --- # Hermes Docker on Kraken ## Key Rule Hermes runs via **`docker compose`** from the `hermes-build/` directory. ## Directory Layout (on Kraken) ``` /srv/.../docker/ ├── hermes-build/ ← build + run from here │ ├── Dockerfile ← clones upstream NousResearch/hermes-agent │ ├── docker-compose.yml ← canonical compose file │ ├── config.yaml ← unused at build time (runtime config is in hermes/config/) │ └── .env ← all secrets/API keys (sourced by compose) └── hermes/ └── config/ ← mounted as /opt/data (HERMES_HOME) inside container ``` ## Compose File (`hermes-build/docker-compose.yml`) ```yaml services: kraken: build: context: . dockerfile: Dockerfile image: hermes-kraken:latest container_name: hermes-kraken stdin_open: true tty: true restart: unless-stopped volumes: - /srv/.../docker/hermes/config:/opt/data - /home/kraken/obsidian:/vault:rw environment: - HERMES_ALLOW_ROOT_GATEWAY=1 env_file: - .env command: gateway network_mode: host ``` ## Managing the Container ```bash cd /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/docker/hermes-build docker compose up -d # start docker compose down # stop docker compose pull # pull new image (if from registry) docker compose build # rebuild image from Dockerfile docker compose restart # restart docker compose logs -f # follow logs ``` ## Image Built locally from `Dockerfile` — clones **upstream** `NousResearch/hermes-agent` (`--depth=1` from `main`). Not a custom fork. To update Hermes: ```bash cd /srv/.../docker/hermes-build docker compose build --no-cache docker compose up -d ``` ## Volumes | Host path | Container path | Purpose | |-----------|----------------|---------| | `hermes/config/` | `/opt/data` | `HERMES_HOME` — config, sessions, skills, memory | | `/home/kraken/obsidian` | `/vault` | Obsidian vault read/write access | ## SSH из контейнера → хост (диагностика и фиксы) > Перенесено из удалённого скилла `kraken-omv-maintenance` / `kraken-docker-ssh`. Актуальные пути и фиксы для случая, когда `ssh kraken-host` изнутри контейнера `hermes-kraken` не работает. **Архитектура** ``` Container (hermes-kraken) uid=10000 (dropped from root by entrypoint) HOME=/opt/data ~/.ssh/config = /opt/data/.ssh/config (bind mount from host) /etc/ssh/ssh_config — bind mount (полный /etc/ssh/) Host (Kraken RPi5) User kraken (uid=1000) owns /home/kraken/.ssh/ /home/kraken/.ssh/id_ed25519 — закрытый ключ (обязательно 600) /home/kraken/.ssh/authorized_keys — должен содержать публичный ключ ``` **Fixed points (НЕ менять)** 1. В compose **не задавать** `user:`, `entrypoint:`, `HERMES_UID/GID` — entrypoint сам сбрасывает uid (image defaults). 2. Публичный ключ на хост один раз: `ssh-copy-id kraken@`. 3. Закрытый ключ на хосте **обязан быть 600**: `ssh kraken@ "chmod 600 /home/kraken/.ssh/id_ed25519"` — OpenSSH 10.x (Bookworm) отвергает 644. 4. В `/etc/passwd` контейнера должен быть `kraken:x:1000` — не выставлять `user:` в compose. 5. SSH config обязан лежать в `$HOME/.ssh/config` (`$HOME=/opt/data` → `/opt/data/.ssh/config`). **Диагностика по порядку** ```bash ssh kraken@ "echo OK && hostname" # доступен ли хост ssh kraken@ "sudo docker ps --filter name=hermes-kraken --format '{{.Names}} {{.Status}}'" # изнутри контейнера на хост (loopback алиас kraken-host) ssh kraken@ "sudo docker exec hermes-kraken ssh -o ConnectTimeout=5 kraken-host 'echo HELLO'" # verbose — какие конфиги читает SSH ssh kraken@ "sudo docker exec hermes-kraken ssh -v kraken-host 'echo HELLO' 2>&1 | grep -E 'config|debug1.*/ssh|Host|connect|Permission|authenticate|bad|owner' | head -15" ssh kraken@ "sudo docker exec hermes-kraken getent passwd 1000" # uid 1000 в passwd? ssh kraken@ "sudo docker exec hermes-kraken ls -la /opt/data/.ssh/id_ed25519" ssh kraken@ "sudo docker exec hermes-kraken cat /opt/data/.ssh/config" ssh kraken@ "sudo docker exec hermes-kraken sh -c 'echo HOME=\$HOME; ls -la \"\$HOME/.ssh/config\"'" ``` **Pitfalls** - Не менять `user:` в compose — entrypoint должен стартовать как root, чтобы сделать chown. - Не chown'ить `/opt/data` на `1000:1000` — entrypoint делает `chown -R hermes:hermes` (uid 10000). - Не ставить 644 на закрытый ключ. - НЕ использовать `Include ~/.ssh/config` в системном `/etc/ssh/ssh_config` — OpenSSH не резолвит `~` в системных Include. - Если `$HOME=/opt/data`, а конфиг живёт в `/opt/data/home/.ssh/` — SSH его не прочитает (путь не совпадает). - Может быть несколько пар ключей: bind mount приносит хост-ключ, а агент может иметь свой в `/opt/data/home/.ssh/`. - passwd сбрасывается при пересоздании контейнера. - Полный mount `/etc/ssh/` перезаписывает и host keys тоже. ## Notes - `network_mode: host` — port 8642 is directly on the Kraken host, no port mapping needed - `HERMES_ALLOW_ROOT_GATEWAY=1` — required since container runs as root - All secrets in `hermes-build/.env` (not committed)