Nous Research의 Hermes Agent를 Ubuntu 리눅스 서버에 Docker 전용으로 설치하고, 같은 서버에서 이미 Docker로 돌고 있는 Mattermost에 봇으로 연결한 실제 과정을 정리한 글입니다. 공식 문서: https://hermes-agent.nousresearch.com/docs/user-guide/docker
이 글의 실제 예시 환경
- 서버: Ubuntu (
your-server), amd64, root 계정- Mattermost: 이미 설치 완료 (docker compose, nginx + HTTPS, 도메인
mattermost.example.com)- Mattermost 컨테이너:
nginx_mattermost,docker-mattermost-1,docker-postgres-1- Mattermost 도커 네트워크 이름:
mattermost(bridge)- Hermes 데이터 디렉터리:
/root/.hermes(컨테이너의/opt/data에 마운트)- Hermes compose:
/root/hermes/docker-compose.yml- 생성한 봇:
agents-bot(Display Name:Agent Bot)
0. M1 Mac 가이드와의 차이 (핵심)
이 글은 기존 Hermes Agent — M1 Mac Docker 설치(macOS)를 Ubuntu 서버용으로 옮긴 것입니다. 바뀌는 지점은 딱 3가지입니다.
| 항목 | M1 Mac | Ubuntu 서버 (이 글) |
|---|---|---|
| 셸 / alias 파일 | ~/.zshrc |
~/.bashrc (root 기준) |
| 아키텍처 | arm64, 가끔 --platform 필요 |
amd64 네이티브, --platform 불필요 |
| Mattermost 접근 주소 | host.docker.internal:8065 |
http://mattermost:8065/ (도커 네트워크 공유) |
⚠️ 가장 중요한 차이 —
host.docker.internal이 여기선 안 됨. Ubuntu의 Mattermost가 nginx 버전으로 떠 있으면 8065 포트를 호스트에 노출하지 않습니다(호스트엔 nginx의 80/443만 열림). 8065는mattermost도커 네트워크 내부에서만 열려 있으므로, Hermes 컨테이너를 그 네트워크에 합류시키고 서비스 별칭mattermost로 접속해야 합니다.
1. 사전 준비
1-1. Docker 확인
docker --version
docker compose version
# 없으면: curl -fsSL https://get.docker.com | sh
1-2. Mattermost 네트워크 · 컨테이너 확인 (연동의 전제)
docker network ls | grep mattermost
docker ps --format 'table {{.Names}}\t{{.Networks}}' | grep -i mattermost
기대 결과:
fa58cd368ffa mattermost bridge local
nginx_mattermost mattermost
docker-mattermost-1 mattermost
docker-postgres-1 mattermost
- 네트워크 이름이
mattermost인지 확인 (compose의external참조에 이 이름을 씀). - 앱 컨테이너 이름은
docker-mattermost-1이지만, 같은 네트워크에서는 서비스 별칭mattermost로 접근 가능 →http://mattermost:8065.
2. 데이터 디렉터리 + 초기 setup 마법사 (최초 1회)
mkdir -p /root/.hermes
docker run -it --rm \
-v /root/.hermes:/opt/data \
nousresearch/hermes-agent setup
마법사 선택지 (이 환경 기준):
| 프롬프트 | 선택 | 이유 |
|---|---|---|
| Select terminal backend | Keep current (local) |
Hermes가 이미 컨테이너 안에서 돎 = 컨테이너 자체가 샌드박스. Docker 선택 시 Docker-in-Docker(소켓 마운트 필요)로 실패 |
| Connect a messaging platform? | Skip — set up later |
이 setup 컨테이너는 mattermost 네트워크에 없어 연결 검증이 실패함. Mattermost는 뒤에서 .env로 직접 설정 |
- API 키(
ANTHROPIC_API_KEY등)는 대화형으로 입력 →/root/.hermes/.env에 저장. - 완료 화면(
✓ Setup Complete!) 뒤의s6-rc: ... stopping로그는 일회용 컨테이너 정상 종료 과정이며 에러가 아님.
3. Mattermost 봇 계정 + 토큰 생성 (연동 필수 선행)
Hermes가 Mattermost에 붙으려면 봇 계정과 Access Token이 필요합니다.
-
Mattermost 관리자 로그인 →
System Console → Integrations → Bot Accounts→ Enable Bot Account Creation = true (저장) -
메인 화면 → 좌상단 메뉴 →
Integrations → Bot Accounts → Add Bot Account -
폼 입력:
항목 값 비고 Username 예: agents-bot필수. 봇의 실제 핸들 Display Name Agent Bot선택 (표시 이름) Role Member대화형 에이전트엔 충분 (System Admin 불필요) post:all ✅ Enabled 봇이 DM + 모든 채널에 응답 가능해짐 (필수) post:channels 체크 불필요 post:all이 상위 권한(DM 포함)이라 커버됨 -
Create Bot Account → 다음 화면의
Token: ...를 즉시 복사 (⚠️ 이 화면에서만 보임, 놓치면 재발급 필요)
왜
post:all인가: 사람이 봇에게 DM으로 말 걸면 봇이 DM으로 답장합니다.post:channels(공개 채널 전용)만으론 DM 응답이 막힙니다.
4. Hermes Docker Compose 작성
/root/hermes/docker-compose.yml:
services:
hermes:
image: nousresearch/hermes-agent:latest
container_name: hermes
restart: unless-stopped
command: gateway run
ports:
- "8642:8642" # OpenAI 호환 gateway API / 헬스체크
- "127.0.0.1:9119:9119" # 대시보드 — 외부 비공개, SSH 터널로만 접근
volumes:
- /root/.hermes:/opt/data # 절대경로 사용 (compose는 ~ 확장 안 함)
environment:
- HERMES_DASHBOARD=1
- HERMES_DASHBOARD_HOST=127.0.0.1
shm_size: "1g" # 브라우저 툴(Playwright)용
mem_limit: 4g # 일반 compose에서 실제 제한 걸리는 키
cpus: 2.0
networks:
- mattermost # Mattermost와 같은 네트워크에 합류
networks:
mattermost:
external: true # Mattermost가 이미 만든 네트워크 재사용
핵심 포인트:
volumes는 절대경로/root/.hermes— compose YAML은 셸이 아니라~를 확장하지 않습니다.~/.hermes로 쓰면./~/.hermes라는 엉뚱한 폴더가 생김.networks.mattermost.external: true— "이 네트워크는 내가 만들지 말고 기존mattermost를 붙여만 써라". Mattermost가 먼저 떠 있어야 함(없으면network mattermost not found).- 대시보드 포트
127.0.0.1:9119:9119— 서버 외부엔 안 열림. 접속은 SSH 터널 (섹션 8 참고).
생성 후 문법 검증:
cd /root/hermes && docker compose config
5. .env에 Mattermost 접속값 기입
/root/.hermes/.env(= 컨테이너 /opt/data/.env)를 편집:
nano /root/.hermes/.env
추가/수정:
MATTERMOST_URL=http://mattermost:8065/
MATTERMOST_TOKEN=<3번에서 복사한 봇 토큰>
MATTERMOST_ALLOWED_USERS=<허용할 username, 쉼표구분>
MATTERMOST_HOME_CHANNEL= # 선택 (섹션 7 참고, 비워도 됨)
MATTERMOST_URL은mattermost(호스트 아님). 내부 도커 DNS가docker-mattermost-1로 라우팅.MATTERMOST_TOKEN은 봇 Access Token. 붙여넣을 때 공백/줄바꿈이 안 섞이도록 주의.MATTERMOST_ALLOWED_USERS는 봇과 대화 가능한 사용자 화이트리스트(보안 게이트). 값은 이메일이 아니라 username. 비워두면 버전에 따라 전체 잠금/전체 허용이 될 수 있어 본인 계정을 명시하는 게 안전.
username을 모를 때(컨테이너 안에서 인증 없이 조회):
docker exec -it docker-mattermost-1 mmctl --local user list
mmctl은 호스트가 아니라 Mattermost 컨테이너 안에 있습니다.docker exec로 컨테이너의 바이너리를 직접 호출하세요(mmctl --local은 컨테이너 내부 무인증 관리 채널).
공인 URL 대안:
MATTERMOST_URL=https://mattermost.example.com도 동작하지만(nginx 경유), 인터넷을 한 바퀴 돌고 드물게 hairpin NAT 문제가 있을 수 있어 내부 주소를 권장.
6. 기동 및 연결 검증
cd /root/hermes
docker compose up -d
6-1. 네트워크 도달
docker exec hermes bash -c \
'cat < /dev/null > /dev/tcp/mattermost/8065 && echo OPEN || echo CLOSED'
# => OPEN 이면 컨테이너 → mattermost:8065 도달 확인
6-2. 게이트웨이 상태 파일 (판단의 근거)
docker exec hermes cat /opt/data/gateway_state.json
아래처럼 "platforms":{"mattermost":{"state":"connected",...}} 이고
error_message가 null이면 성공:
{"gateway_state":"running", ...,
"platforms":{"mattermost":{"state":"connected","error_code":null,"error_message":null}}}
docker logs hermes | grep mattermost가 비어 보여도 실패가 아닙니다(연결 로그가 tail 밖으로 밀렸을 뿐). 판단은 로그 문구가 아니라gateway_state.json의state필드로.
6-3. 실제 대화 테스트 (진짜 검증)
https://mattermost.example.com로그인 (allowed_users에 넣은 계정)- 좌측 Direct Messages →
+→Agent Bot(봇) 검색 → DM 열기 - 아무 메시지 전송 → 봇이 답하면 end-to-end 완료
응답이 없으면 실시간 로그를 띄워 두고 다시 말 걸기:
docker logs -f hermes
7. 홈 채널 설정 (선택)
홈 채널 = Hermes가 cron 작업 결과·크로스플랫폼 메시지를 능동적으로 보내는 기본 채널. 사람이 DM으로 묻는 대화는 홈 채널 없이도 동작하므로 필수는 아님.
방법 A — 슬래시 명령 (가장 간단, 즉시 반영)
홈으로 쓸 DM/채널 입력칸에서:
/sethome
→ 그 대화가 홈 채널로 지정됨(재시작 불필요). 전용 채널로 할 거면 봇을 그 채널에 먼저 초대해야 함.
방법 B — .env로 관리 (소스 오브 트루스)
MATTERMOST_HOME_CHANNEL에는 채널 ID(26자)를 넣습니다(표시 이름/@유저명 아님).
채널 ID 얻기:
# 브라우저: 채널명 클릭 → View Info → Channel ID 복사
# 또는 터미널:
docker exec docker-mattermost-1 mmctl --local team list
docker exec docker-mattermost-1 mmctl --local channel list <팀이름> --json \
| python3 -c 'import json,sys;[print(c["name"],c["id"]) for c in json.load(sys.stdin)]'
.env 기입 후 재시작 필요(.env는 시작 시점에만 읽힘):
nano /root/.hermes/.env # MATTERMOST_HOME_CHANNEL=<채널ID>
cd /root/hermes && docker compose restart hermes
형식이 헷갈리면: 원하는 채널에서
/sethome을 한 번 실행 → Hermes가 저장한 값을 그대로 확인해.env에 복사하면 형식 오류가 없음.docker exec hermes sh -c 'grep -ri home /opt/data/config.yaml /opt/data/gateway_state.json 2>/dev/null'
8. 운영 · 편의 설정
8-1. hermes CLI를 호스트에서 실행 (alias, Ubuntu는 bashrc)
Hermes를 호스트에 네이티브 설치하지 않고, docker exec를 짧은 이름으로 감싸
호스트에서 hermes 한 단어로 컨테이너 안 CLI에 접속합니다.
echo "alias hermes='docker exec -it hermes /opt/hermes/.venv/bin/hermes'" >> ~/.bashrc
source ~/.bashrc
사용:
hermes # 대화형 CLI 접속
hermes doctor # 진단
hermes --tui # TUI 인터페이스
hermes config # 현재 설정 보기
alias 구조: 첫 hermes는 컨테이너 이름(compose의 container_name: hermes),
뒤의 /opt/hermes/.venv/bin/hermes는 컨테이너 안의 실행 파일. 즉 "hermes 컨테이너에
들어가 그 안의 hermes 바이너리를 실행".
전제·주의:
- 컨테이너가 실행 중이어야 함(
docker exec기반). 꺼져 있으면Error: No such container: hermes→cd /root/hermes && docker compose up -d먼저. 상태 확인:docker ps --filter name=hermes가Up ...이면 OK. - 이 CLI 세션은 게이트웨이(Mattermost 봇 서비스)와 별개의 대화 세션입니다. 게이트웨이는
봇으로 계속 돌고,
hermes는 사람이 직접 대화하는 용도 — 둘 다 같은/opt/data를 공유. - alias 없이 매번 풀 명령으로도 가능:
docker exec -it hermes /opt/hermes/.venv/bin/hermes - M1 글 6·7번의 "깨진 네이티브 stub"(
~/.local/bin/hermes)은 과거install.sh네이티브 설치를 시도한 Mac에서만 생깁니다. Docker로만 설치한 이 서버엔 해당 없음. 혹시which hermes가 alias가 아닌 경로를 가리키면 그때만 정리.
8-2. 대시보드 접속 (외부 비공개 → SSH 터널)
대시보드는 127.0.0.1:9119에 바인딩되어 서버 외부엔 열려 있지 않습니다.
로컬 PC에서 터널을 뚫어 접속:
# 로컬 PC 터미널에서
ssh -L 9119:localhost:9119 root@<서버IP>
# 그 후 브라우저: http://localhost:9119
8-3. 로그 · 업그레이드
docker logs -f hermes # 실시간 로그
docker compose pull && docker compose up -d # 업그레이드 (설정 자동 마이그레이션)
9. 데이터 영속화 구조 (/root/.hermes = 컨테이너 /opt/data)
| 경로 | 내용 |
|---|---|
.env |
API 키 · Mattermost 토큰/URL/설정 |
config.yaml |
모델 등 설정 |
sessions/ |
대화 이력 |
memories/ |
영속 메모리 |
skills/ |
설치된 확장 (기본 번들 72개) |
cron/ |
예약 작업 |
logs/ |
런타임 로그 |
gateway_state.json |
게이트웨이/플랫폼 연결 상태 |
컨테이너는 지워도 /root/.hermes만 있으면 그대로 복구됩니다.
요약 체크리스트
-
docker --version/docker compose version확인 -
docker network ls | grep mattermost— 네트워크 이름mattermost확인 -
mkdir -p /root/.hermes+... hermes-agent setup(terminal=local, messaging=skip) - Mattermost 봇 생성 (
agents-bot, Role=Member, post:all Enabled) → 토큰 복사 -
/root/hermes/docker-compose.yml작성 (절대경로 볼륨 +external: mattermost) -
docker compose config로 문법 검증 -
/root/.hermes/.env에MATTERMOST_URL=http://mattermost:8065/+ 토큰 + allowed_users -
cd /root/hermes && docker compose up -d - 네트워크
OPEN+gateway_state.json의mattermost.state=connected확인 - Mattermost에서 봇 DM 테스트 → 응답 확인 (end-to-end)
- (선택) 홈 채널:
/sethome또는.env의MATTERMOST_HOME_CHANNEL+restart - (선택)
~/.bashrcalias, 대시보드 SSH 터널