---
title: "Claude Code CLI 를 OAuth 구독 계정으로 Docker 에서 헤드리스 자동화하기"
description: "API 키가 아니라 구독(OAuth) 계정으로 Claude Code 를 cron 스케줄러에서 무인 실행하며 만난 함정 4개와 해결을 정리했습니다. 컨테이너 안에서 1회 로그인하고 설정 디렉토리를 볼륨으로 영속시키는 구성입니다."
date: 2026-10-23
updated: 2026-10-23T07:04:10.818Z
tags: [claude-code, docker, oauth, automation]
canonical: https://blog.wooncloud.com/posts/claude-code-oauth-headless-docker
---

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 라 그대로 막히죠.

![root 로 실행하면 --dangerously-skip-permissions 가 거부되지만, USER node 로 전환하면 같은 명령이 통과한다.](/images/posts/claude-code-oauth-headless-docker/536b8463-97e9-410e-8280-27c9d5362c03.webp)

공식 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`) 바로 진행됩니다.

![macOS 호스트의 토큰은 Keychain 에 있어 파일로 복사할 수 없으므로, 컨테이너에서 한 번 로그인해 토큰 파일을 named volume 에 영속시키면 cron 스케줄러가 재로그인 없이 claude 를 계속 실행한다.](/images/posts/claude-code-oauth-headless-docker/bef36189-a37b-49a0-ae3d-0c35200a3305.svg)

## 최종 구성은 이렇습니다

```dockerfile
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"]
```

```js
// 헤드리스 one-shot 실행: stdin 을 닫아 대기 경고를 없앤다
spawn('claude', ['--dangerously-skip-permissions', '-p', prompt], {
  stdio: ['ignore', 'pipe', 'pipe'],
});
```

```yaml
# docker-compose: 설정 디렉토리를 named volume 으로 영속
services:
  scheduler:
    build: .
    restart: unless-stopped
    volumes:
      - claude-creds:/home/node/.claude   # 1회 로그인 토큰 영속
volumes:
  claude-creds:
```

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

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

## 참고

- https://docs.docker.com/reference/dockerfile/#user
- https://nodejs.org/api/child_process.html#optionsstdio
