Code 실전

Claude Code Hooks로 배포 안전망 만들기: staging·prod 환경 분기 자동 차단

seunghyeonlab 2026. 5. 19. 10:02

hero

배포 직전 손이 떨리는 경험, 한 번이라도 있다면 이 글이 그 불안을 없애줄 수 있다. staging에서 멀쩡하던 게 prod에서 터지는 이유는 대부분 사람이 판단했기 때문이다. Claude Code의 훅 레이어를 쓰면 그 판단을 구조로 옮길 수 있다.

전체 흐름 다이어그램


1. 왜 지금 이걸 봐야 하나

배포 파이프라인에는 항상 같은 구멍이 있다. CI/CD가 통과해도, 코드 리뷰가 끝나도, 실제로 명령을 실행하는 순간만큼은 사람 손이 들어간다. DROP TABLE, rm -rf, 외부 API 직접 호출 — 이런 것들이 staging에서는 아무 문제 없다가 prod에서 롤백을 부른다.

문제의 구조는 단순하다. 환경을 확인하는 시점이 너무 늦다. 명령을 입력하고 나서야 "아, 여기가 prod였지"를 깨닫는다. 그때는 이미 늦었다.

Claude Code의 PreToolUse 훅은 이 구조를 바꾼다. 도구가 실행되기 직전에 끼어들어서, 환경을 먼저 확인하고 통과·차단을 결정한다. 사람이 판단하기 전에 구조가 먼저 개입하는 방식이다.

문제 구조 흐름


2. 핵심 아이디어

PreToolUse 훅에서 DEPLOY_ENV를 읽고, 명령이 실행되기 전에 통과·차단을 결정한다.

훅은 공항 보안 검색대다. 비행기에 타기 전에 짐을 검사한다. 탑승 후에 검사하면 의미가 없다. PostToolUse에 안전 로직을 붙이면 이미 실행된 다음이다. 훅의 위치가 전부다.

환경별로 금지 명령의 종류가 다르다는 점도 중요하다.

환경 차단 대상 이유
production DROP, TRUNCATE, DELETE FROM, rm -rf 되돌릴 수 없는 파괴 명령
staging 외부 도메인 curl/wget 실서비스 API 오염 방지
development 없음 자유롭게 실험

3. 바로 따라하는 방법

훅 등록

먼저 .claude/settings.json에 훅 엔트리를 추가한다. BashWrite 두 도구 모두 게이팅해야 한다. Bash만 걸면 Write 도구로 설정 파일을 직접 덮어쓰는 경우가 빠져나간다.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.claude/hooks/env-gate.sh"
          }
        ]
      },
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.claude/hooks/write-gate.sh"
          }
        ]
      }
    ]
  }
}

env-gate.sh 작성

~/.claude/hooks/ 디렉터리를 만들고 아래 스크립트를 저장한다.

#!/usr/bin/env bash
# env-gate.sh — 환경별 분기 안전망

ENV="${DEPLOY_ENV:-development}"
TOOL_INPUT=$(cat)

if echo "$TOOL_INPUT" | grep -q '"command"'; then
  CMD=$(echo "$TOOL_INPUT" | python3 -c \
    "import sys,json; print(json.load(sys.stdin).get('command',''))")
fi

# production: 파괴적 명령 차단
if [ "$ENV" = "production" ]; then
  if echo "$CMD" | grep -qiE '(DROP|TRUNCATE|DELETE FROM|rm -rf)'; then
    echo '{"decision":"block","reason":"prod 환경에서 파괴적 명령은 차단됩니다"}'
    exit 0
  fi
fi

# staging: 외부 도메인 API 호출 차단
if [ "$ENV" = "staging" ]; then
  if echo "$CMD" | grep -qE 'curl|wget|fetch' \
     && ! echo "$CMD" | grep -q 'staging\.internal'; then
    echo '{"decision":"block","reason":"staging은 내부 도메인만 허용합니다"}'
    exit 0
  fi
fi

echo '{"decision":"approve"}'
chmod +x ~/.claude/hooks/env-gate.sh

검증

# staging 환경으로 외부 curl 시도
DEPLOY_ENV=staging claude -p "curl https://api.stripe.com/v1/charges 실행해줘"
# → 차단 메시지 출력 확인

# production 환경으로 DROP 시도
DEPLOY_ENV=production claude -p "DROP TABLE users 실행해줘"
# → 차단 메시지 출력 확인

실측 기준으로 훅 응답 지연은 평균 18ms였다. 체감상 없는 수준이다.

검증 실행 흐름

PostToolUse: prod 실행 알림 붙이기

차단과 별개로, prod에서 뭔가 실행됐을 때 Slack으로 알림을 보내면 "내가 모르는 사이에 prod에서 뭔가 실행됐다"는 불안이 없어진다.

#!/usr/bin/env bash
# notify-prod.sh — prod 실행 완료 알림

ENV="${DEPLOY_ENV:-development}"
if [ "$ENV" = "production" ]; then
  RESULT=$(cat)
  TOOL_NAME=$(echo "$RESULT" | python3 -c \
    'import sys,json; d=json.load(sys.stdin); print(d.get("tool_name",""))')
  curl -s -X POST "$SLACK_WEBHOOK_URL" \
    -H 'Content-Type: application/json' \
    -d "{\"text\":\"[prod] 훅 실행 완료: ${TOOL_NAME}\"}" > /dev/null
fi

4. 운영할 때 조심할 점

PostToolUse에 안전 로직을 붙이는 실수가 가장 흔하다. 처음 이 구조를 만들 때 PostToolUse에 차단 로직을 넣었다가 전혀 동작하지 않아서 시간을 낭비했다. PostToolUse는 실행이 끝난 뒤에 호출된다. 차단은 반드시 PreToolUse에서 해야 한다.

Write 도구를 빼먹는 실수도 있다. Bash만 게이팅하면 Claude가 Write 도구를 써서 prod.env 파일을 직접 덮어쓰는 경우가 뚫린다. write-gate.sh는 파일 경로 기준으로 prod 설정 파일 패턴(*.prod.*, production.*)을 차단하면 된다.

환경 변수 주입 타이밍도 확인해야 한다. DEPLOY_ENV가 Claude Code 프로세스 시작 시점에 설정되어 있어야 훅에서 읽힌다. CI/CD 파이프라인에서는 export DEPLOY_ENV=production을 Claude 실행 직전에 넣는다.

Mac/Linux 환경 모두 동일하게 동작하지만, Docker 컨테이너 안에서 쓴다면 ~/.claude/hooks/ 경로를 볼륨 마운트로 유지해야 컨테이너 재시작 후에도 훅이 살아있다.


마무리

훅은 안전망이 아니라 교통 신호등이다. PreToolUse에서 막지 않으면 PostToolUse는 이미 늦다. DEPLOY_ENV를 기준으로 BashWrite 모두 게이팅하면, 사람의 실수가 prod에 닿기 전에 구조가 먼저 막아낸다. 3개월 무롤백이 그 결과였다.

다음 글에서는 훅 레이어에 승인 플로우(사람이 직접 y/n을 입력하는 인터랙티브 게이트)를 붙이는 방법을 다룬다.


🐦 X에서 더 빠르게: @baegseungh7061
📚 이 시리즈 더 보기: Code 실전
💌 새 글 알림: X 팔로우 또는 블로그 RSS 구독