146 lines
6.1 KiB
Markdown
146 lines
6.1 KiB
Markdown
---
|
||
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@<host>`.
|
||
3. Закрытый ключ на хосте **обязан быть 600**: `ssh kraken@<host> "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@<host> "echo OK && hostname" # доступен ли хост
|
||
ssh kraken@<host> "sudo docker ps --filter name=hermes-kraken --format '{{.Names}} {{.Status}}'"
|
||
# изнутри контейнера на хост (loopback алиас kraken-host)
|
||
ssh kraken@<host> "sudo docker exec hermes-kraken ssh -o ConnectTimeout=5 kraken-host 'echo HELLO'"
|
||
# verbose — какие конфиги читает SSH
|
||
ssh kraken@<host> "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@<host> "sudo docker exec hermes-kraken getent passwd 1000" # uid 1000 в passwd?
|
||
ssh kraken@<host> "sudo docker exec hermes-kraken ls -la /opt/data/.ssh/id_ed25519"
|
||
ssh kraken@<host> "sudo docker exec hermes-kraken cat /opt/data/.ssh/config"
|
||
ssh kraken@<host> "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)
|