# Cursor에서 Vercel까지 — 조회 앱 배포 실습

Python 3.12 이상 · Cursor · Git · GitHub·Vercel 계정이 필요합니다.

개인 비상업 학습은 Hobby 이용 조건을 확인하고, 업무용은 조직 승인 계정을 사용하세요.
https://vercel.com/docs/plans/hobby

ZIP을 풀고 app.py가 있는 deploy-lab 폴더를 Cursor에서 엽니다.

### 터미널 실행


```powershell
py -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe app.py
```

`py`가 없으면 `python --version`으로 확인한 뒤 첫 명령의 `py`를 `python`으로 바꿉니다. 가상환경 활성화는 필요 없습니다. 브라우저에서 `http://127.0.0.1:5000`, `/api/jobs`, `/health`를 차례로 확인합니다. 화면에는 가상 업무 3건과 `v1`이 보입니다. 종료는 터미널에서 Ctrl+C입니다.

### 환경변수로 제목 바꾸기

서버를 종료하고 같은 PowerShell에서 실행합니다.

```powershell
$env:APP_TITLE = "나의 배포 연습실"
.\.venv\Scripts\python.exe app.py
```

환경변수는 코드 밖에서 전달하는 설정값입니다. 이 값은 현재 터미널 프로세스와 그 자식에 적용되며, GitHub push로 Vercel에 전달되지 않습니다. `APP_TITLE`은 공개할 제목이라 비밀이 아닙니다. 비밀키는 코드에 넣거나 응답·로그에 출력하지 않습니다.

## 06 · GitHub에서 Vercel로

### 1. 실습 저장소 만들기

Cursor의 Source Control에서 **Initialize Repository → 변경 파일 확인 → 메시지 입력 후 Commit → Publish Branch**로 자신의 GitHub 계정에 `week23-deploy-lab` 저장소를 만듭니다. 인증 요청이 나오면 브라우저에서 GitHub 로그인을 완료합니다. 공개 저장소를 선택하기 전에 가상 샘플과 코드만 있는지 확인하세요.

GitHub 저장소 첫 화면에 `app.py`, `requirements.txt`, `.python-version`, `sample-jobs.json`, `templates/`, `.gitignore`, `README.md`가 보여야 합니다. `.venv`, `.env`가 보이면 배포 전에 제외합니다. 비밀이 이미 커밋됐다면 파일을 지우는 것만으로 이력에서 사라지지 않으니 해당 키를 폐기·재발급해야 합니다.

### 2. Vercel에서 가져오기

Vercel Dashboard → **Add New → Project → Import Git Repository**에서 방금 만든 저장소를 선택합니다. 목록에 없으면 GitHub 연결의 저장소 접근 권한을 확인하고 이 실습 저장소만 허용합니다.

| 설정 | 이번 실습 값 |
|---|---|
| 프로젝트 이름 | week23-deploy-lab 또는 사용 가능한 이름 |
| Root Directory | app.py가 있는 저장소 루트 |
| Framework Preset | 자동 감지된 Flask 확인 |
| Build Command / Output Directory | 기본값 유지, 별도 override 없음 |
| 설치 패키지 | requirements.txt의 Flask |
| Python 버전 | .python-version의 3.12 |
| Environment Variables | APP_TITLE = 나의 배포 연습실 |

Flask가 감지되지 않으면 Root Directory와 커밋된 파일을 먼저 확인합니다. GitHub에 `deploy-lab/` 폴더째 올렸다면 Root Directory를 그 폴더로 지정합니다. 기존 교육 사이트의 설정 파일을 복사하지 않습니다. Render용 `gunicorn` 시작 명령이나 `python app.py`를 Build Command에 넣지 않습니다.

Deploy를 누르고 배포 상태가 Ready인지 확인합니다. 실패하면 **Build Logs**에서 첫 오류를 확인합니다. 성공 후 프로젝트의 **Production 도메인**을 엽니다. Preview 주소는 변경 검토용이고 Production은 운영 브랜치의 배포 주소입니다. Git 연결 설정에서 Production Branch가 실습 브랜치인 `main`인지 확인합니다.

### 3. 다른 기기에서 확인

1. Production 주소를 휴대폰이나 다른 브라우저에서 엽니다. 가상 업무 3건·제목·버전을 확인합니다.
2. 같은 주소 뒤에 `/api/jobs`, `/health`를 붙입니다. JSON 응답과 `status: ok`를 확인합니다.
3. 브라우저 주소창의 사이트 정보에서 HTTPS 인증서를 확인하고, 개발자 도구 Network에서 응답 상태 200을 확인합니다.
4. 아래 명령의 예시 도메인을 실제 프로젝트 도메인으로 바꿉니다. `https://`와 경로를 빼고 입력합니다. 결과에 여러 주소나 별칭이 나와도 정상일 수 있습니다.

```powershell
nslookup your-project.vercel.app
```

Vercel 로그인 화면이 나오면 앱의 로그인 기능이 아니라 **Deployment Protection**일 수 있습니다. Production 주소와 프로젝트 보호 설정을 확인합니다. 이번 가상 데이터 앱을 공개하려는 경우에만 해당 프로젝트 설정을 조정하고, 조직 정책이 있으면 강사 시연으로 대체합니다.

## 07 · 수정·로그·복구까지 해보기

1. `app.py`의 `RELEASE_LABEL = "v1"`을 `"v2"`로 바꿉니다. 로컬 서버를 재시작해 화면과 `/health`를 확인합니다.
2. Cursor에서 변경 내용을 확인해 **버전 문구 변경만 별도 commit → push**합니다.
3. Vercel Deployments에서 새 commit과 Ready 상태를 확인합니다. Production 주소에서 `v2`를 확인합니다. 이전 배포의 고유 URL을 보고 있으면 계속 `v1`일 수 있습니다.
4. `/api/jobs`에 접속한 뒤 **Runtime Logs**에서 요청 경로·200·`release=v2`를 확인합니다. Build Logs는 설치·배포 준비, Runtime Logs는 실행 중 요청을 확인하는 곳입니다. 로그 보관 기간도 요금제의 영향을 받습니다.
5. 환경변수를 바꾸려면 Settings → Environment Variables에서 `APP_TITLE`의 Production 값을 수정하고 **새로 배포**합니다. 이미 만들어진 배포에 자동 반영되는 것으로 생각하지 않습니다.

### 코드 변경을 되돌리는 연습

터미널에서 아래 첫 명령으로 버전 변경 commit의 해시를 찾습니다. 두 번째 명령의 `VERSION_COMMIT_HASH`를 실제 해시로 바꿉니다. 작업 폴더의 미커밋 변경이 없는 상태에서 실행하세요.

```powershell
git log --oneline -5
git revert --no-edit VERSION_COMMIT_HASH
git push
```

기존 이력을 지우지 않고 변경을 되돌리는 commit이 추가됩니다. 재배포 후 화면과 `/health`가 `v1`이면 성공입니다. **코드 복구는 환경변수·외부 DB·첨부파일 복구가 아닙니다.** 이번에는 버전 한 줄만 바꿨기 때문에 되돌릴 범위를 쉽게 확인할 수 있습니다.

| 증상 | 확인할 곳 |
|---|---|
| ModuleNotFoundError | requirements.txt와 설치 로그, 로컬 .venv 사용 여부 |
| 배포 404 | Root Directory, Flask 감지, app.py의 app 진입점 |
| 500 또는 함수 실행 오류 | Runtime Logs, JSON·templates가 GitHub에 포함됐는지 |
| 제목이 바뀌지 않음 | 로컬 재시작, Vercel 환경변수의 대상과 새 배포 여부 |
| push했는데 예전 화면 | Production Branch, 배포 상태, 접속한 도메인과 commit |
| localhost 접속 실패 | 실행 중 터미널과 포트 5000 확인, 이전 서버는 Ctrl+C 종료 |

`/health`의 ok는 이 앱이 응답한다는 뜻입니다. DB·로그인·모든 기능이 정상임을 보장하는 종합 검사는 아닙니다.

## 08 · 저장·권한·비용은 배포 후에도 남는다

이번 `sample-jobs.json`은 **코드와 함께 배포되는 읽기 전용 자료**입니다. 수정해서 commit·배포하면 새 버전에 포함됩니다. 사용자가 업로드하거나 TinyDB가 실행 중 저장한 파일과는 다릅니다.

Vercel의 실행 환경에 영구 쓰기를 기대하지 않습니다. 임시 공간이 있어도 사용자 데이터의 보관소로 쓰지 않습니다. 여러 실행 인스턴스가 하나의 메모리·파일을 공유한다고 가정할 수도 없습니다. 실제 앱은 PostgreSQL 같은 외부 DB, 객체 스토리지, 인증·인가와 함께 설계합니다. Render도 기본 로컬 파일은 재시작·재배포 등에서 유지되지 않습니다.

비용은 **저장량뿐 아니라 요청 수·함수 실행·데이터 전송·빌드·로그**에도 영향을 받습니다. 요금제와 사용량 화면을 확인하고, 무료 한도를 무한 사용량으로 해석하지 않습니다. Hobby는 한도 초과 시 이용이 제한될 수 있고, 유료 사용은 지출 관리 설정을 확인해야 합니다. 예산 알림이 곧 사용 중단 장치라고 가정하지 않습니다. [Vercel 요금제](https://vercel.com/docs/plans/hobby) · [지출 관리](https://vercel.com/docs/pricing/spend-management)

실습 종료 후 필요 없으면 **실습 프로젝트**의 Settings → General에서 Delete Project를 사용합니다. 교육 사이트 프로젝트와 이름을 구별하세요. GitHub 저장소는 별도로 남으며, 연결한 외부 DB·스토리지·유료 구독이 있다면 각각 종료 여부를 확인해야 합니다. 이번 앱은 외부 DB·스토리지를 만들지 않습니다.

### 완료 체크

- 다른 기기에서 Production URL을 열고 업무 3건을 확인했다.
- 환경변수의 제목과 `/health` 응답을 확인했다.
- v1 → v2 재배포와 revert → v1 복구를 확인했다.
- Runtime Logs에서 요청을 찾고, 코드 배포와 파일 영구 저장의 차이를 설명할 수 있다.
- 사용량과 프로젝트 정리 위치를 확인했다.
