Skip to content
게으른 엔지니어의 기술 블로그
Go back

Docker에서 Hermes 돌리다가 uid 하나 때문에 하루를 갈아넣은 이야기

VPS에 Hermes라는 Agent를 Docker로 띄워놓고 텔레그램으로 쓰고 있다. 어느 날 터미널에서도 인터랙티브하게 쓰고 싶어졌다. 다행히 이건 금방 됐다 — 이미지 안에 hermes chat이라는 서브커맨드가 있어서 텔레그램 봇 프로세스는 안 건드리고 docker exec -it로 같은 컨테이너에 별도 세션만 붙이면 됐다.

문제는 그다음이었다. Hermes가 “파일을 저장했다”고 알려주는데, 정작 내 계정으로는 그 디렉토리를 열어볼 수조차 없었다.

$ ls -la ~/hermes-data/
ls: cannot open directory '/home/decipher/hermes-data/': Permission denied

여기서부터 거의 하루를 갈아넣게 될 줄은 몰랐다.

왜 안 보이지

$ docker exec hermes ls -la /opt/data/
-rw------- 1 hermes hermes 6422 Jul  7 09:59 2026-07-07-news-summary.md

컨테이너 안에서 Hermes는 root가 아니라 전용 유저(uid 10000)로 돈다. 그 유저가 만든 파일은 600 권한이라 다른 유저는 아예 못 본다. 처음엔 “그럼 그룹을 묶으면 되지 않나” 싶었다. 근데 확인해보니 데이터 폴더 자체가 700이라 그룹 비트가 아예 0이고 게다가 앱이 이 폴더를 주기적으로 다시 잠근다는 것까지 알게 됐다. 여긴 아예 포기하고 원래 있던 별도의 workspace 폴더(호스트 소유로 마운트해둔)를 정식 교환 통로로 쓰기로 했다.

그런데 Hermes한테 직접 물어보니 재밌는 답이 돌아왔다. “거기 쓰려고 했는데 권한이 없어서, 제가 전체 권한 가진 제 홈 디렉토리 밑에 새로 만들어서 저장했다”는 거였다. 정식 통로라고 믿었던 곳도 사실 Hermes 자신은 못 쓰는 곳이었던 거다.

그룹을 만들어봤다

호스트에 Hermes의 gid(10000)와 맞는 그룹을 새로 만들고 내 계정을 거기 넣은 다음, workspace 폴더를 그 그룹 소유로 바꾸고 setgid 비트까지 걸었다.

sudo groupadd -g 10000 hermes-share
sudo usermod -aG hermes-share decipher
sudo chgrp hermes-share ~/hermes-workspace
sudo chmod 2775 ~/hermes-workspace   # setgid: 새로 생기는 파일도 이 그룹 상속

셸에서 직접 테스트해보니 됐다 — 됐다고 생각했다.

$ docker exec --user 10000:10000 hermes sh -c "echo test > /opt/data/workspace/.perm-test"
$ ls -la ~/hermes-workspace/.perm-test
-rw-r--r-- 1 10000 hermes-share 5 Jul  7 19:24 .perm-test

그런데 실제로 Hermes가 저장 기능으로 파일을 다시 만들자 또 600으로 나왔다. setgid는 그룹만 물려줄 뿐, Hermes가 저장 직후 스스로 거는 chmod 600까지는 못 막는다는 걸 그제야 알았다. 그룹이 맞아도 파일 자체의 권한 비트가 잠겨 있으면 아무 소용이 없다.

ACL도 걸어봤는데

“그럼 새로 생기는 파일마다 기본적으로 그룹 권한을 강제로 부여하면?” 싶어서 POSIX 기본 ACL을 걸었다.

setfacl -d -m g:hermes-share:rwx ~/hermes-workspace
setfacl -m g:hermes-share:rwx ~/hermes-workspace

근데 이것도 안 됐다. uid 10000으로 파일을 만들고 chmod 600까지 걸어본 뒤 확인해보니:

$ getfacl ~/hermes-workspace/.acltest2
group:hermes-share:rwx	#effective:---
mask::---

chmod를 부르면 ACL의 mask 값까지 같이 재계산되면서 걸어둔 권한이 무효화된다는 걸 이 한 줄(#effective:---)로 확인했다. 어떤 앱이 저장 후 명시적으로 chmod를 부르는 한, 호스트 쪽에서 그룹이나 ACL로 손쓰는 건 다 이긴다는 걸 알았다.

다음으로 “몇 분마다 권한을 강제로 풀어주는 cron을 돌리면 어떨까” 생각이 들었는데, 이건 스스로 반려했다. 근본 원인을 고친 게 아니라 증상만 주기적으로 지우는 거고 폴링 지연까지 생기니 딱히 좋은 방법이 아니었다.

결국 Hermes한테 직접 물어봤다

바이너리를 뒤져도 관련 설정이 안 보여서 제일 빠른 길은 Hermes한테 직접 물어보는 거였다. “config로는 안 되지만, 내 메모리에 규칙을 등록해서 저장할 때마다 스스로 chmod를 실행하게 할 수 있다”는 답이 왔고 실제로 그렇게 등록했다. 테스트해보니 정말 됐다.

근데 이 방법도 찜찜했다. 파일 하나 저장할 때마다 “chmod 해야 하나?”를 매번 다시 판단하고 별도 tool call을 하나 더 실행하는 구조라, 저장할 때마다 토큰이 조금씩 더 든다. LLM의 “기억”에 기대는 방식이라 100% 확정적이지도 않고.

더 근본적인 질문

여기서 잠깐 멈추고 다시 생각해봤다. 애초에 이 Agent를 Docker로 격리해서 돌리는 게 지금 용도에 맞는 방식인가? 텔레그램으로 채팅만 하고 컨테이너 안에서 알아서 일하게 하는 거면 격리가 전혀 문제 될 게 없다. 근데 나는 호스트와 파일을 계속 주고받고 싶은 건데, 그러면 Docker가 원래 잘하는 일(격리)이 오히려 지금 내가 원하는 일(교환)과 정면으로 부딪히는 거였다.

진짜 답

docker-compose.ymluser: "1000:1000"을 직접 넣어봤다. 컨테이너가 뜨다가 죽었다. 근데 로그에 뜬 에러 메시지가 의외로 친절했다.

[hermes] ERROR: container started with --user 1000 (an arbitrary, non-hermes UID) — not supported.

To make container-written files match your HOST user, don't use --user.
Start as root (the default) and pass your host UID/GID instead:

    docker run -e HERMES_UID=$(id -u) -e HERMES_GID=$(id -g) ...

이 이미지는 부팅 초기에 root 권한이 필요한 구조라 --user로 강제로 다른 uid를 주면 그 초기화 단계가 깨진다는 것, 대신 전용 환경변수를 쓰라는 안내였다.

environment:
  - HERMES_UID=1000
  - HERMES_GID=1000

이거였다. 이 두 줄만 넣고 재시작하니 로그에 이렇게 찍혔다.

[stage2] Changing hermes UID to 1000
[stage2] Changing hermes GID to 1000

이미지가 부팅 시점에 내부 유저를 호스트 uid로 재매핑하고 데이터 볼륨까지 통째로 chown해준 거다. 확인해보니:

$ cat ~/hermes-data/auth.json > /dev/null && echo OK
OK

우회 없이 바로 읽힌다. 그동안 만들었던 그룹도, ACL도, Hermes 메모리에 등록해둔 규칙도 전부 필요 없어졌다. 하나씩 지우고 원래대로 되돌렸다. 텔레그램도 재시작 후 멀쩡히 잘 됐다.

정리

돌아보면 하루 종일 붙잡고 있던 문제가 결국 환경변수 두 줄로 끝났다. 다음에 Docker로 뭔가 새로 띄울 때는 이 순서를 지키려고 한다:

그룹 만들고 ACL 걸고 하는 건 다 근본 원인(uid 불일치)을 못 건드린 채 증상만 쫓아간 삽질이었다. 이미지가 지원하는 정식 방법 하나를 처음부터 썼으면 하루를 아낄 수 있었을 거다.


Share this post:

Previous Post
요즘 뭐 하고 있나
Next Post
리눅스 ACL이 뭐길래 — POSIX ACL과 mask 이해하기