2026-10-19 · Cursor · Flask · GitHub · Vercel · DNS · HTTPS
지난주에는 내 컴퓨터에 파일을 저장했습니다. 이번에는 Cursor에서 실행한 작은 조회 앱을 인터넷 주소로 열고, 수정한 코드가 배포되는 과정을 확인합니다. 다른 기기에서 같은 화면을 보고, 문제가 나면 로그와 버전으로 원인을 찾는 것이 완료 기준입니다.
01 · 이미 배운 것과 연결하기
| 표시 | 기존 자료 | 이번에 이어지는 내용 |
|---|---|---|
| 복습 | 11주차 GitHub 업로드, 13주차 Git 실습 | 코드 저장·commit·push |
| 확장 | 18주차 프론트엔드 | 정적 HTML 전달과 Python 실행의 차이 |
| 복습 | 19주차 Worker 실행 | 코드 파일과 실행 중인 프로그램은 다름 |
| 확장 | 20주차 인증·인가 | 공개 URL이 생겨도 사용자 권한 검사는 별개 |
| 확장 | 21주차 TinyDB, 22주차 저장 정책 | 배포 환경에서는 영구 저장 위치와 비용을 다시 설계 |
| NEW | 이번 주 | DNS·HTTPS·환경변수·배포 로그·재배포 |
22주차 앱에는 로컬 TinyDB와 첨부파일이 있습니다. 이번 실습은 독립적인 읽기 전용 조회 앱입니다. 이전 앱의 로그인·DB·파일 저장까지 통합해서 공개하는 과정은 후속 과제입니다.
02 · 수업 전에 준비하기
- GitHub에 로그인할 수 있는지 확인합니다. 가입 과정은 11주차 안내를 참고합니다.
- Vercel 가입에서 GitHub 계정으로 가입하고 필요한 이메일 인증을 끝냅니다. GitHub 로그인과 저장소 접근 권한 연결은 별도 단계일 수 있습니다.
- Cursor와 Python 3.12 이상, Git을 준비합니다. 터미널에서
py --version,git --version을 확인합니다. ZIP의 배포용 Python 버전은 3.12로 지정했습니다. - 실습 전용 개인 저장소를 사용합니다. 회사 자료·기존
.env·API 키를 옮기지 않습니다.
개인 비상업 학습용 앱은 Hobby 요금제의 이용 조건을 확인해 진행합니다. 회사·팀의 업무용 사용까지 무료라고 가정하지 않습니다. 해당하지 않는 경우 조직에서 승인한 계정 또는 강사 시연으로 진행합니다. 유료 체험·카드 등록·도메인 구매를 필수로 요구하지 않습니다. 계정 연결이 어려우면 로컬 실습을 마친 뒤 강사의 배포·로그 화면을 관찰해도 됩니다. Hobby 이용 조건
03 · 주소를 열면 어디로 갈까?
localhost는 지금 브라우저를 실행하는 컴퓨터를 가리킵니다. 다른 사람에게 http://127.0.0.1:5000을 보내면 그 사람의 컴퓨터에 접속합니다. localhost는 IPv4의 127.0.0.1 또는 IPv6의 ::1로 연결될 수 있습니다.
| 용어 | 뜻 | 이번 실습 |
|---|---|---|
| 서버 | 요청을 받아 응답하는 프로그램 또는 그 프로그램을 실행하는 컴퓨터 | Flask가 HTML·JSON 응답을 만듦 |
| IP | 네트워크에서 통신할 주소 | 로컬에서는 127.0.0.1 |
| 포트 | 한 컴퓨터에서 통신을 받을 프로그램을 구별하는 번호 | 로컬 앱은 5000, HTTPS 기본 포트는 443 |
| 도메인 | 사람이 읽기 쉬운 이름 | Vercel에서 제공하는 프로젝트 주소 |
| DNS | 도메인의 레코드를 조회하는 체계 | 이름으로 접속할 곳을 찾음 |
| HTTPS | HTTP 통신에 TLS 암호화와 인증서 검증을 적용 | 브라우저와 접속 대상 사이의 전송 보호 |
요청의 흐름
브라우저 → DNS 조회 → HTTPS 연결 → Vercel → Flask → HTML·JSON 응답 → 브라우저
DNS 결과나 연결은 재사용될 수 있으므로 매 요청마다 모든 단계를 새로 하는 것은 아닙니다. 인증서는 접속 대상 도메인을 검증하는 데 쓰입니다. HTTPS가 있어도 로그인·인가·데이터 보관 정책은 따로 필요합니다.
DNS의 A는 IPv4 주소, AAAA는 IPv6 주소, CNAME은 다른 도메인 이름을 가리킵니다. 이번에는 제공된 도메인을 사용하므로 DNS 레코드를 직접 바꾸지 않습니다. 직접 구매한 도메인을 연결할 때 서비스에서 안내하는 값을 설정합니다. 반영 시간은 캐시와 TTL 등의 영향을 받으며 항상 같은 시간이 걸리지는 않습니다.
04 · Vercel과 Render는 무엇이 다를까?
두 서비스 모두 GitHub 저장소를 연결해 앱을 배포할 수 있습니다. 배포에는 코드·설치할 패키지·환경 설정·실행 환경이 함께 필요합니다. 정적 사이트에 .py 파일을 올리는 것만으로 Python 서버가 실행되지는 않습니다.
| 구분 | Vercel | Render |
|---|---|---|
| 이번 Flask 앱 실행 | Flask 앱을 Vercel Function으로 배포 | 시작 명령으로 웹 서비스 실행 |
| 설정 관점 | 지원되는 진입점의 app을 인식 |
패키지 설치와 서버 시작 명령 지정 |
| 교육에서 보여주는 것 | 간단한 배포·환경변수·재배포 | 서버 프로세스·포트·worker 운영으로 확장 |
| 로컬 파일 영구 보관 | 외부 DB·스토리지 설계 필요 | 기본 파일시스템은 임시, 유료 영구 디스크 선택 가능 |
이번 실습은 Vercel을 사용합니다. app.py의 app = Flask(...)가 배포 진입점이고, if __name__ == "__main__" 아래의 app.run(...)은 로컬 실행용입니다. 배포 환경에서는 Vercel이 앱을 불러 실행합니다. 함수 실행 방식이라고 매 요청마다 반드시 새 프로세스를 만든다는 뜻은 아닙니다. Vercel Flask 문서 · Render Flask 문서 · Render 무료 저장소 제한
빌드는 패키지 설치·변환·배포 묶음 준비 단계이고, 실행은 사용자의 요청에 응답하는 단계입니다. 이번 앱은 별도 빌드 스크립트를 작성하지 않습니다. 자동 배포 연결만으로 테스트가 자동 작성·실행되는 것은 아닙니다.
이름만 먼저 알아두기
- 웹 서버·애플리케이션 서버: 정적 파일 전달, 요청 중계, 업무 코드 실행 같은 역할을 나누어 부릅니다. 반드시 서로 다른 컴퓨터일 필요는 없습니다.
- Nginx·리버스 프록시: 앱 앞에서 요청을 받아 전달하거나 정적 파일을 제공하는 구성에 쓰입니다. 이번에는 플랫폼이 담당하는 영역이라 직접 설치하지 않습니다.
- Docker: 앱과 의존성을 이미지로 묶고 컨테이너로 실행하는 도구입니다. 설정·외부 DB·운영체제 차이까지 모두 없애지는 않습니다.
- CI/CD: 변경을 검사·통합하고 전달·배포하는 자동화 과정입니다. 이번에는 GitHub push에 따른 배포를 관찰합니다. 별도 테스트 파이프라인은 만들지 않습니다.
05 · Cursor에서 조회 앱 실행하기
ZIP을 풀고 app.py가 있는 deploy-lab 폴더만 Cursor의 File → Open Folder로 엽니다. 기존 교육 사이트 폴더 안에서 새 Git 저장소를 만들지 않습니다. 다른 위치의 새 폴더를 사용하세요. 완성 예제로 바로 실행하거나, 새 빈 폴더에서 아래 프롬프트로 만들어 결과를 비교합니다.
Cursor에 붙여넣을 프롬프트
Python 3.12와 Flask==3.1.3으로 공개용 읽기 전용 업무 조회 앱을 만들어줘.
app.py에 app = Flask(...)를 노출하고 Vercel의 Flask 지원을 사용해줘.
파일은 app.py, templates/index.html, sample-jobs.json, requirements.txt,
.python-version(3.12), .gitignore, README.md로 구성해줘.
가상 업무 3건만 사용하고 실제 개인정보·로그인·DB·업로드 기능은 넣지 마.
GET /는 제목·업무 3건·버전을, GET /api/jobs는 jobs와 version을 JSON으로,
GET /health는 status=ok와 version을 JSON으로 반환해줘.
제목은 APP_TITLE 환경변수로 바꾸고 기본값은 '배포 연습실'로 해줘.
app.py의 RELEASE_LABEL = "v1"을 화면과 API에 표시해줘.
JSON과 템플릿은 app.py 기준 절대 경로로 읽고 파일에는 쓰지 마.
스타일은 HTML 안에 넣고 모바일에서 가로 넘침이 없게 해줘.
로그에는 메서드·경로·상태 코드·버전만 남기고 응답은 no-store로 설정해줘.
로컬은 main 가드에서 127.0.0.1:5000, debug=False로 실행해줘.
.venv, .env 및 .env.*, .vercel, __pycache__는 Git에서 제외해줘.
README에 Windows PowerShell 실행과 Vercel GitHub 가져오기 절차를 넣어줘.
터미널 실행
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에서 실행합니다.
$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. 다른 기기에서 확인
- Production 주소를 휴대폰이나 다른 브라우저에서 엽니다. 가상 업무 3건·제목·버전을 확인합니다.
- 같은 주소 뒤에
/api/jobs,/health를 붙입니다. JSON 응답과status: ok를 확인합니다. - 브라우저 주소창의 사이트 정보에서 HTTPS 인증서를 확인하고, 개발자 도구 Network에서 응답 상태 200을 확인합니다.
- 아래 명령의 예시 도메인을 실제 프로젝트 도메인으로 바꿉니다.
https://와 경로를 빼고 입력합니다. 결과에 여러 주소나 별칭이 나와도 정상일 수 있습니다.
nslookup your-project.vercel.app
Vercel 로그인 화면이 나오면 앱의 로그인 기능이 아니라 Deployment Protection일 수 있습니다. Production 주소와 프로젝트 보호 설정을 확인합니다. 이번 가상 데이터 앱을 공개하려는 경우에만 해당 프로젝트 설정을 조정하고, 조직 정책이 있으면 강사 시연으로 대체합니다.
07 · 수정·로그·복구까지 해보기
app.py의RELEASE_LABEL = "v1"을"v2"로 바꿉니다. 로컬 서버를 재시작해 화면과/health를 확인합니다.- Cursor에서 변경 내용을 확인해 버전 문구 변경만 별도 commit → push합니다.
- Vercel Deployments에서 새 commit과 Ready 상태를 확인합니다. Production 주소에서
v2를 확인합니다. 이전 배포의 고유 URL을 보고 있으면 계속v1일 수 있습니다. /api/jobs에 접속한 뒤 Runtime Logs에서 요청 경로·200·release=v2를 확인합니다. Build Logs는 설치·배포 준비, Runtime Logs는 실행 중 요청을 확인하는 곳입니다. 로그 보관 기간도 요금제의 영향을 받습니다.- 환경변수를 바꾸려면 Settings → Environment Variables에서
APP_TITLE의 Production 값을 수정하고 새로 배포합니다. 이미 만들어진 배포에 자동 반영되는 것으로 생각하지 않습니다.
코드 변경을 되돌리는 연습
터미널에서 아래 첫 명령으로 버전 변경 commit의 해시를 찾습니다. 두 번째 명령의 VERSION_COMMIT_HASH를 실제 해시로 바꿉니다. 작업 폴더의 미커밋 변경이 없는 상태에서 실행하세요.
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 요금제 · 지출 관리
실습 종료 후 필요 없으면 실습 프로젝트의 Settings → General에서 Delete Project를 사용합니다. 교육 사이트 프로젝트와 이름을 구별하세요. GitHub 저장소는 별도로 남으며, 연결한 외부 DB·스토리지·유료 구독이 있다면 각각 종료 여부를 확인해야 합니다. 이번 앱은 외부 DB·스토리지를 만들지 않습니다.
완료 체크
- 다른 기기에서 Production URL을 열고 업무 3건을 확인했다.
- 환경변수의 제목과
/health응답을 확인했다. - v1 → v2 재배포와 revert → v1 복구를 확인했다.
- Runtime Logs에서 요청을 찾고, 코드 배포와 파일 영구 저장의 차이를 설명할 수 있다.
- 사용량과 프로젝트 정리 위치를 확인했다.
09 · 다음 질문: 이 앱에 AI를 붙이면?
다음에는 공개된 조회 앱에 AI 기능을 붙일 때 무엇이 달라지는지 살펴봅니다. 지금은 고정 샘플을 읽는 앱입니다. 일반 프로그램도 입력·상태·시간·외부 서비스에 따라 결과가 달라질 수 있습니다. AI를 붙이면 여기에 출력의 변동성, 평가, 호출 비용과 실패 처리가 더해집니다.
공식 문서 확인: 2026-10-19. 서비스 화면 이름과 이용 조건은 바뀔 수 있습니다. Flask 배포 · Python 런타임 · 환경변수