cron 예약 실행과 클라우드 실행¶
한 줄 요약: "매일 18시에 이 작업을 돌려라"를 맡길 곳은 내 컴퓨터(cron·launchd)와 클라우드 서버(GitHub Actions 등) 두 가지다. 둘을 가르는 건 "실행 시각에 그 기계가 켜져 있느냐"와 "그 기계가 재료 파일을 볼 수 있느냐"다.
📍 만난 상황¶
- 2026-11-11 — 학습 일지를 매일 18시에 자동으로 정리해 push하는 구조를 만들던 중이었다. 2번 물음 → 그날 일지
- 10:03 직전에 "Mac에 18시 launchd 예약을 등록했다"는 보고를 들었다. "꼭 이 Mac에서 cron을 돌려야 하나? 노트북을 바꾸면 안 되잖아"라고 물었다. → 대화 원본이 그 컴퓨터에만 있어서 완전히 클라우드로만은 불가능하다는 답을 들었다.
- 10:50 할 일 정리를 들은 뒤 "실행 위치를 어디로 하면 좋을까, 세션을 안 닫아도 18시에 정리되면 좋겠다"고 물었다. → 노트북 교체는 두 방식의 차이가 아니고, 실제 차이는 18시에 노트북이 깨어 있느냐 하나라는 답을 들었다.
🧩 쉽게 말하면¶
매일 저녁 6시에 일기 정리를 누군가에게 맡긴다고 하자.
- 집 비서(내 컴퓨터): 내 책상 위 메모를 다 볼 수 있다. 하지만 6시에 집이 불 꺼져 있으면(노트북을 덮었으면) 일을 못 한다.
- 회사 비서(클라우드): 6시엔 항상 출근해 있다. 하지만 내 책상 메모는 못 본다. 내가 미리 사진 찍어 보내 준 것만 정리할 수 있다.
예약 실행이 없으면 매일 6시에 내가 직접 명령을 쳐야 한다. 잊는 날이 반드시 생긴다.
📖 제대로 알기¶
예약 실행(스케줄링)이란¶
정해진 시각이나 간격에 프로그램을 자동으로 실행하는 기능이다. 사람이 없어도 돈다.
로컬 예약 실행¶
- cron: 유닉스·리눅스의 전통적인 예약 실행 도구다.
crontab(cron 표: 언제 무엇을 돌릴지 적은 목록)에 한 줄씩 적는다. - cron 식:
0 18 * * *처럼 칸 다섯 개로 시각을 적는다. 순서는 분·시·일·월·요일이다. 이 예는 "매일 18시 0분"이다. - launchd: macOS의 공식 예약·상주 실행 관리자다. Mac에서도 cron이 돌긴 하지만 Apple은 launchd를 권한다. 설정은
~/Library/LaunchAgents/의 plist(XML 설정 파일)에 적는다.
클라우드 예약 실행¶
- GitHub Actions: GitHub가 제공하는 자동 실행 서비스다. 저장소 안
.github/workflows/*.yml에 적으면 GitHub 서버가 돌려 준다. - schedule 트리거: Actions에서 cron 식으로 시각을 정하는 방법이다. 시각은 UTC(세계 표준시) 기준이다. 한국 18시는 UTC 9시다.
- 러너(runner: 작업을 실제로 돌리는 임시 서버): 실행마다 새로 만들어지고 끝나면 사라진다. 그래서 저장소에 올라온 것만 들고 시작한다.
- Secrets: 토큰 같은 비밀값을 저장소 설정에 숨겨 두고 실행 때만 넣어 주는 기능이다.
⚙️ 어떻게 동작하나¶
로컬(launchd) 방식¶
- launchd가 plist를 읽어 "매일 18:00"을 기억한다.
- 18:00이 되면 지정한 스크립트를 실행한다.
- 스크립트는 Mac의 파일(예: 대화 기록
~/.claude/projects/*.jsonl)을 바로 읽는다. - 정리 결과를 commit하고 push한다.
- 그 시각에 Mac이 잠자기 상태였다면, launchd는 깨어날 때 놓친 실행을 한 번 돌린다. 전원이 꺼져 있었다면 그날 실행은 없다.
클라우드(GitHub Actions) 방식¶
- 낮 동안 Mac이 재료(정리 메모)를 주기적으로 GitHub에 올린다. 이 부분은 여전히 Mac의 일이다.
- UTC 9:00(한국 18:00)에 GitHub가 러너를 하나 띄운다.
- 러너가 저장소를 내려받는다(checkout). 올라온 재료만 손에 쥔다.
- Secrets의 토큰으로 정리 작업을 돌리고 push한다.
- 러너가 사라진다. Mac이 꺼져 있어도 1~5는 그대로 일어난다.
flowchart LR
subgraph L["① 로컬 방식"]
M1["Mac 파일"] -->|"18시 직접 읽기"| J1["launchd 작업"]
end
subgraph C["② 클라우드 방식"]
M2["Mac 파일"] -->|"낮 동안 업로드"| G["GitHub 저장소"]
G -->|"18시 checkout"| J2["Actions 러너"]
end
J1 -->|"push"| R["일지 저장소"]
J2 -->|"push"| R
①은 재료를 직접 읽지만 Mac이 깨어 있어야 한다. ②는 Mac이 꺼져도 돌지만, 재료가 업로드라는 다리를 먼저 건너야 한다.
🧪 예시¶
- 예시 1 · 일상에 빗대어: 오늘 대화가 09:00~17:55에 있었다고 하자. 업로드는 20분마다 한다.
- ① 로컬: 18:00에 Mac이 켜져 있으면 17:55 대화까지 전부 들어간다. 17:30에 덮고 퇴근했다면 실행은 다음 날 아침 Mac을 열 때로 밀린다. 퇴근길에 볼 일지가 없다.
-
② 클라우드: 마지막 업로드가 17:40이었다면 17:40~17:55의 15분은 그날 정리에서 빠진다. 대신 17:30에 덮고 나가도 18:00에 일지가 나온다.
-
예시 2 · 실제로는: ① launchd plist의 핵심 부분이다.
①<key>ProgramArguments</key> <array> <string>/bin/zsh</string> <string>-lc</string> <string>~/learning-log/.tools/run.sh</string> </array> <key>StartCalendarInterval</key> <dict> <key>Hour</key><integer>18</integer> <key>Minute</key><integer>0</integer> </dict>ProgramArguments: 실행할 명령이다.zsh -lc로 감싸 로그인 셸 환경(PATH 등)을 불러온다. ②StartCalendarInterval: 매일 18시 0분에 실행하라는 뜻이다. cron의0 18 * * *와 같다.
② GitHub Actions 워크플로의 핵심 부분이다.
on:
schedule:
- cron: "0 9 * * *" # UTC 9시 = 한국 18시
workflow_dispatch: # 손으로도 실행 가능
jobs:
digest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./.tools/run.sh
env:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
schedule.cron: 시각을 UTC로 적는다. 한국 시각으로 적는 실수가 흔하다.
② actions/checkout: 저장소를 러너에 내려받는다. 러너가 볼 수 있는 건 이것뿐이다.
③ secrets.…: 저장소 설정에 등록한 토큰을 실행 때만 넣는다. 파일에는 값이 남지 않는다.
⚖️ 비교 · 흔한 오해¶
| 항목 | ① 로컬 (launchd) | ② 클라우드 (Actions) |
|---|---|---|
| 내 파일 직접 읽기 | 됨 | 안 됨 (올린 것만) |
| 18시에 노트북 덮음 | 깨어날 때 실행 | 정상 실행 |
| 18시 직전 대화 | 전부 들어감 | 마지막 업로드 이후는 빠짐 |
| 구성 | 단순 | 업로드 + 워크플로 + 토큰 |
| 노트북 교체 | clone + 설치 | clone + 설치 |
오해 1. "클라우드 예약 작업도 내 Mac 파일을 읽을 수 있다" 러너는 GitHub 데이터센터의 임시 서버다. 내 Mac과 네트워크로 이어져 있지 않다. 그래서 "Mac에만 있는 재료"는 누군가 먼저 올려 줘야 한다. 왜 헷갈리기 쉬운가: 둘 다 "내 저장소의 스크립트"를 돌린다. 같은 코드를 돌리니 같은 파일을 볼 거라고 느끼기 쉽다. 하지만 코드는 같아도 실행되는 기계가 다르다.
오해 2. "노트북을 바꾸면 로컬 방식은 못 쓴다"
스크립트와 설정을 저장소에 넣어 두면 새 노트북에서 git clone + 설치 스크립트로 똑같이 복구된다. 클라우드 방식도 업로드 부분은 새 노트북에 설치해야 한다. 그래서 교체 문제는 두 방식의 차이가 아니다. 오늘 Claude도 처음엔 이걸 클라우드의 장점처럼 설명했다가 바로잡았다.
오해 3. "cron 시각에 정확히 돈다" Mac은 잠자기 중이면 못 돈다. GitHub Actions의 schedule도 사용량이 몰리면 수 분~수십 분 늦거나 드물게 건너뛴다. 정각이 꼭 중요하면 이 점을 감안한다.
🛠️ 실무에서는¶
- 고르는 기준은 하나다: 실행 시각에 그 기계가 깨어 있나? 재료는 그 기계에 있나?
- 재료가 내 컴퓨터에만 있고, 그 시각에 켜져 있다 → 로컬.
- 그 시각에 꺼져 있을 수 있다 → 클라우드. 단, 재료를 올리는 장치를 따로 둔다.
- 체크리스트
- 스크립트·설정을 저장소에 넣었나? (교체 대비)
- 클라우드라면 시각을 UTC로 적었나?
- 비밀값은 파일이 아니라 Keychain·Secrets에 두었나?
- 실행 결과를 남기는 로그나 알림이 있나? (놓친 날을 알아채려면)
- 안 돌았을 때 먼저 볼 것
- launchd:
launchctl list | grep <이름>으로 등록 여부, 스크립트 로그의 실행 시각. - Actions: 저장소의 Actions 탭 실행 기록. 저장소에 60일간 활동이 없으면 예약 실행이 꺼질 수 있다(공개 저장소).
- 처음엔 단순한 쪽(로컬)으로 며칠 써 보고, 놓치는 날이 생기면 옮기는 게 안전하다. 정리 로직을 스크립트로 분리해 두면 옮겨도 재사용된다.
❓ 자주 묻는 질문¶
- Q. Mac에서 cron과 launchd 중 무엇을 쓰나? A. launchd를 권한다. cron은 잠자기 중 놓친 실행을 그냥 건너뛰지만, launchd는 깨어날 때 한 번 돌려 준다.
- Q. 클라우드로 가면 Mac에서 할 일이 아예 없어지나? A. 재료가 Mac에만 생기면 없어지지 않는다. 재료를 올리는 가벼운 작업(주기적 업로드, 세션 종료 hook 등)이 남는다.
- Q. 회사 서버에 cron을 걸면 되지 않나? A. 항상 켜진 서버라는 점에선 클라우드와 같다. 다만 그 서버도 내 Mac 파일은 못 보니 업로드 문제는 똑같이 남는다.
🔭 더 깊이 보기¶
- 놓친 실행을 다시 돌릴 때 같은 날을 두 번 처리하지 않으려면 어떻게 하나 — 재시도와 중복 방지(멱등성)는 자동화의 다음 단골 문제라서.
- 이벤트 기반 실행(hook, push 트리거)은 시각 기반 예약과 무엇이 다른가 — "언제"가 아니라 "무슨 일이 생겼을 때" 돌리는 방식이라서.
- 관련 주제: CLI headless 실행과 claude -p, 작업 큐와 백그라운드 워커, 데이터 파이프라인 실행과 갱신
📚 참고 자료¶
- GitHub Actions 설명서 (GitHub Docs 한국어) — 워크플로 작성,
schedule트리거, Secrets 사용법 · 링크 - Creating Launch Daemons and Agents (Apple 개발자 문서, 영문) — plist 작성법과
StartCalendarInterval의 잠자기 동작 · 링크 - crontab guru (영문) — cron 식을 넣으면 언제 도는지 말로 풀어 주는 도구 · 링크
🗓️ 이력¶
- 2026-11-11: 클라우드에서 돌리면 노트북 교체 문제가 없지 않냐, 두 실행 위치가 실제로 어떻게 다르냐를 2번 물었다 → 일지