본문으로 건너뛰기
wooncloud

Claude Code CLI 를 OAuth 구독 계정으로 Docker 에서 헤드리스 자동화하기

API 키가 아니라 구독(OAuth) 계정으로 Claude Code 를 cron 스케줄러에서 무인 실행하며 만난 함정 4개와 해결을 정리했습니다. 컨테이너 안에서 1회 로그인하고 설정 디렉토리를 볼륨으로 영속시키는 구성입니다.

·5 min read·— views··

Claude Code 를 cron 스케줄러로 무인 자동화하고 싶었습니다. 다만 API 키 과금이 아니라 구독(OAuth) 계정으로 돌려야 했습니다.

"한 번 로그인해 두면 토큰이 남아서 헤드리스로 계속 도는" 구조를 Docker 로 만들었는데, 막상 해 보니 함정이 4개 있었습니다.

호스트 토큰은 컨테이너로 옮길 수 없습니다

macOS 는 OAuth 토큰을 Keychain 에 저장합니다. ~/.claude/.credentials.json 같은 파일이 아예 없으니, 호스트의 로그인 상태를 컨테이너로 복사하려는 시도가 막힙니다.

반대로 생각하면 됩니다. 컨테이너는 Linux 라서 Keychain 이 없고, 토큰을 평범한 JSON 파일로 저장합니다. 그 디렉토리를 named volume 으로 영속시키면 "1회 로그인 후 계속 헤드리스" 가 성립합니다.

호스트 토큰을 옮기는 게 아니라, 컨테이너 안에서 1회 로그인하는 게 정답이었습니다.

root 에서는 권한 우회 플래그가 거부됩니다

헤드리스 실행에는 권한 프롬프트를 건너뛰는 --dangerously-skip-permissions 가 필요한데, 이 플래그는 root 나 sudo 로 실행하면 보안상 거부됩니다. 기본 Docker 이미지는 root 라 그대로 막히죠.

공식 node 이미지에 있는 node(uid 1000) 유저로 전환하면 통과합니다. 글로벌 npm 설치 같은 root 작업을 다 끝낸 뒤, 마지막에 USER node 를 둡니다.

설정 파일이 컨테이너를 다시 만들 때마다 사라집니다

크리덴셜과 설정 파일이 서로 다른 경로(~/.claude/ 와 홈 루트의 ~/.claude.json)에 흩어져 있어서, 볼륨을 한쪽만 마운트하면 다른 쪽은 컨테이너를 재생성할 때마다 초기화됩니다.

CLAUDE_CONFIG_DIR 환경변수로 볼륨 경로를 지정하면 크리덴셜, 설정, 백업이 전부 그 안에 모입니다. 한 볼륨으로 영속되고 설정 파일 not-found 경고도 사라집니다.

-p 모드가 stdin 을 3초 기다립니다

비대화형 실행인데 stdin 이 열려 있으면 "no stdin data received in 3s" 경고와 함께 지연됩니다. 자식 프로세스를 띄울 때 stdin 을 ignore 로 닫으면(또는 < /dev/null) 바로 진행됩니다.

최종 구성은 이렇습니다

FROM node:22-bookworm
RUN npm install -g @anthropic-ai/claude-code   # root 권한 필요 작업 먼저
WORKDIR /app
COPY . .
ENV CLAUDE_CONFIG_DIR=/home/node/.claude        # 크리덴셜과 설정을 한 곳에
RUN mkdir -p /home/node/.claude && chown -R node:node /home/node /app
USER node                                       # 마지막에 비-root 전환
CMD ["node", "scheduler.mjs"]
// 헤드리스 one-shot 실행: stdin 을 닫아 대기 경고를 없앤다
spawn('claude', ['--dangerously-skip-permissions', '-p', prompt], {
  stdio: ['ignore', 'pipe', 'pipe'],
});
# docker-compose: 설정 디렉토리를 named volume 으로 영속
services:
  scheduler:
    build: .
    restart: unless-stopped
    volumes:
      - claude-creds:/home/node/.claude   # 1회 로그인 토큰 영속
volumes:
  claude-creds:

처음 한 번은 인터랙티브 컨테이너에서 OAuth URL 을 호스트 브라우저로 열어 인증합니다. 토큰이 볼륨에 떨어지면 이후 스케줄러가 그 볼륨을 그대로 재사용합니다.

토큰은 refresh 토큰으로 자동 갱신되니, 볼륨이 유지되는 한 재로그인은 거의 필요 없었습니다.

참고