<!-- canonical: https://docs.axelabs.ai/ops/runbook/deploy -->
<!-- source: content/ops/runbook/deploy.mdx -->

---
title: Blue/Green Deploy
description: frame 무중단 배포 절차.
playbook: true
---

# Blue/Green Deploy (frame)

## AI 요청 프롬프트

```
https://docs.axelabs.ai/ops/runbook/deploy 따라 frame [customer] Blue/Green 배포해줘.

진행:
1. 현재 active color 확인 (`docker exec axe-frame-proxy` + network alias) + git log origin/main..HEAD 변경 미리보기
2. 자동 경로 (`axe deploy frame {customer} --apply`) 가능 판단, 안 되면 수동 9 step 분기
3. 페이지의 각 step 실행 + 매 step 결과 받고 다음. 특히 alias swap 직전 사용자 확인 (passive health 60s 통과 검증 후) + 기존 active stop 전 60s grace 대기
4. 함정 발생 시 페이지 본문 따라 우회 (health check 60s timeout / in-flight grace 미준수 / mcp-proxy upstream 미반영)
5. 새 active 첫 요청 검증 (`curl https://axe.axelabs.ai/frame/health` 200) + (선택) /ops/updates Ship Log
```

본인 AI session = Claude Code / Cursor / ChatGPT 데스크탑 / Claude.app / 기타.

페이지 본문 = 사람이 직접 read 도 가능, AI 도 참고. AI 가 본 페이지 fetch 후 위 진행 순서대로 사용자와 step-by-step interactive 풀어나감.

다운타임 0 으로 frame 새 코드 적용.

## 자동 (권장)

```bash
# Dry-run 으로 변경사항 미리 확인
axe deploy frame axe

# 실제 적용
axe deploy frame axe --apply
```

`axe deploy frame {customer}` 가 자동으로 처리:

| Step | 작업 |
|---|---|
| 1 | git pull origin main |
| 2 | passive container 의 새 코드로 build (예: green 이 passive 면 green build) |
| 3 | passive container start |
| 4 | 헬스체크 통과 대기 (poll `/health/ready`, max 60s) |
| 5 | docker network alias `frame-mcp` 이동 (active → passive) |
| 6 | 새 active 의 첫 요청 정상 처리 검증 |
| 7 | 기존 active (현재 passive) 의 in-flight 요청 60s grace |
| 8 | 기존 active stop |
| 9 | 운영자에게 deploy 완료 알림 |

총 5-7 분 (build 시간 포함). 사용자 측 다운타임: 0.

## 수동 (응급, axe CLI 사용 불가 시)

### 1. 현재 active color 확인

```bash
docker exec axe-frame-proxy cat /etc/caddy/Caddyfile     # mount 위치 (frame-proxy/docker-compose.yml 의 :ro) | grep upstream
# 또는
docker network inspect frame_default | grep -A 2 'frame-mcp"'
# Aliases 에 `frame-mcp` 표시된 컨테이너가 active
```

active = blue 가정.

### 2. green 에 새 코드 build

```bash
cd /Users/axe/frame
git pull origin main

set -a && source .env.local && set +a
docker compose build frame-mcp-green
docker compose up -d --force-recreate frame-mcp-green
```

### 3. green health check

```bash
sleep 10
curl -s http://localhost:3711/health/ready
# → {"status":"ok"}
```

### 4. alias 이동 (blue → green)

```bash
# blue 에서 alias 제거
docker network disconnect frame_default frame-mcp-blue
docker network connect frame_default frame-mcp-blue   # alias 없이 다시

# green 에 alias 부여
docker network disconnect frame_default frame-mcp-green
docker network connect --alias frame-mcp frame_default frame-mcp-green
```

> 위 절차는 axe-frame-proxy 의 upstream resolution 이 즉시 갱신. Caddy 가 alias 기반이라 DNS 캐시 없음.

### 5. axe-frame-proxy 갱신 확인

```bash
docker exec axe-frame-proxy cat /etc/caddy/Caddyfile     # mount 위치 (frame-proxy/docker-compose.yml 의 :ro) | grep frame-mcp
# upstream 이 frame-mcp:3710 그대로 (alias 가 green 으로 이동)
```

### 6. blue 의 in-flight 요청 완료 대기 + stop

```bash
sleep 60   # in-flight grace
docker stop frame-mcp-blue
# 다음 deploy 시 새 코드로 build 됨 (현재 passive)
```

## 검증

```bash
# 외부 호출 확인
curl -s https://axe.axelabs.ai/frame/health
# → 200 OK + service: frame-mcp

# 새 코드 버전 확인
docker exec frame-mcp-green python -c "import frame; print(frame.__version__)"
```

## Rollback (5초)

문제 발견 시 alias 만 되돌리면 끝:

```bash
docker network disconnect frame_default frame-mcp-green
docker network connect frame_default frame-mcp-green
docker network connect --alias frame-mcp frame_default frame-mcp-blue
```

blue 가 다시 active. 새 코드는 green 컨테이너에 그대로 (다음 시도 시 fix 후 재배포).

## 함정

| 함정 | 결과 | 회피 |
|---|---|---|
| `docker compose up --build` 단일 컨테이너로 (블루든 그린이든) | 30-60s 다운타임 | blue+green 동시 운영 필수 |
| `--force-recreate` 없이 env 변경 적용 | 새 env 안 읽음 | 항상 `--force-recreate` |
| customer macmini 에 docker daemon 죽음 | proxy 도 죽음 | `launchctl kickstart` 로 docker 재시작 |
| build 실패 (Dockerfile 오류) | passive 컨테이너 stop | git revert + 재시도, blue 는 영향 없음 |

## 다른 서비스

- **blueprint** — 단일 컨테이너, 짧은 다운타임 수용 (`docker compose up -d --force-recreate blueprint`)
- **vault** (Vaultwarden) — 단일 컨테이너, 다운타임 시 사용자 vault 로그인 불가 (rare op, 야간 작업)
- **stream / magnet** — 각 customer 측 자체 운영
