Claude Code CLI 를 OAuth 구독 계정으로 Docker 에서 헤드리스 자동화하기
API 키가 아니라 구독(OAuth) 계정으로 Claude Code 를 cron 스케줄러에서 무인 실행하며 만난 함정 4개와 해결을 정리했습니다. 컨테이너 안에서 1회 로그인하고 설정 디렉토리를 볼륨으로 영속시키는 구성입니다.
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 토큰으로 자동 갱신되니, 볼륨이 유지되는 한 재로그인은 거의 필요 없었습니다.