바이브코딩 서비스를 Docker 컨테이너로 실전서비스 론칭하기 · 3부 — CI/CD, IaC, 무중단 배포, 롤백

21장. (심화) 프론트를 Vercel로 분리 배포

여기까지 온 것을 축하합니다. 14장에서 ALB 주소로 접속되는 기본 완성을 만들었고, 3부에서 자동 배포와 무중단 배포, 롤백까지 손에 넣었습니다. 그런데 지금 우리 서비스 주소를 친구에게 보내려고 보면 두 가지가 걸립니다. 주소가 http://vote-alb-1234567890.ap-northeast-2.elb.amazonaws.com처럼 길고, 앞이 https가 아니라 http입니다. 브라우저가 "안전하지 않음"이라고 표시하기도 하죠.

이 장은 심화(보너스) 입니다. 지금까지 만든 기본 구조(ECS + ALB)를 없애거나 바꾸지 않습니다. 그 위에, 실무에서 아주 흔히 쓰는 방식 하나를 더 얹습니다. 프론트(화면)만 떼어 내 Vercel이라는 곳에 올리고, 짧은 HTTPS 주소로 접속하게 만드는 것입니다. API와 DB는 지금 그대로 ECS와 RDS를 씁니다. 다 끝내면 https://vote-app.vercel.app 같은 주소로 접속해 투표가 됩니다. 한 단계씩 천천히 따라오세요.

[지금 여기]

우리는 22단계를 거쳐 "라이브 투표 서비스"를 직접 만들고, 컨테이너에 담아, AWS 클라우드에 배포했습니다. 아래가 그 전체 여정 지도입니다. 지금은 21단계, 기본 완성 위에 실무 패턴 하나를 더 얹는 심화 자리에 있습니다.

  1. 1장개발 환경 준비 (터미널 / Node.js / Git / GitHub / VS Code / Claude Code)
  2. 2장Claude Code 사용법 익히기
  3. 3장무엇을 만들지 기획 (라이브 투표 서비스)
  4. 4장로컬에서 서비스 만들기 (React + Express + PostgreSQL)
  5. 5장Docker 개념
  6. 6장Dockerfile 작성
  7. 7장컨테이너 빌드·실행
  8. 8장AWS 시작 (계정 / IAM / 리전 / 비용)
  9. 9장이미지 태깅
  10. 10장ECR에 이미지 올리기
  11. 11장VPC 네트워크 구성
  12. 12장RDS 데이터베이스 생성
  13. 13장SSM 터널로 RDS 초기화
  14. 14장ECS Fargate + ALB로 배포 (기본 완성)
  15. 15장CI/CD (GitHub Actions)
  16. 16장IaC (Terraform)
  17. 17장배포 자동화 연결
  18. 18장현업 운영 구조 이해
  19. 19장무중단 배포
  20. 20장신규 버전 배포 & 롤백
  21. 21장(심화) 프론트를 Vercel로 분리 배포
  22. 22장마무리 (회고 + 리소스 삭제)

이번 장에서 완성되는 것

한 문장으로: https://...vercel.app 형태의 짧은 HTTPS 주소로 접속하면, 14장에서 만든 API·DB(ECS·RDS)를 그대로 쓰면서 투표가 동작합니다.

이렇게 되면 성공입니다: Vercel이 준 HTTPS 주소로 접속해 설문이 뜨고 투표가 되며, 브라우저 개발자 도구의 Network 탭에서 /api 요청이 Vercel 도메인 (https) 으로 나가는 것이 보입니다. 이때 사용자의 브라우저는 오직 HTTPS Vercel하고만 통신하므로, 4장에서 겪었던 CORS나 "안전하지 않은 콘텐츠(혼합 콘텐츠)" 경고가 생기지 않습니다.

전체 흐름을 그림으로 그리면 이렇게 됩니다.

참고

기본 완성은 그대로 둡니다 이 장은 14장의 ALB 주소 접속을 없애지 않습니다. 그 주소도 계속 살아 있습니다. 우리는 그 앞에 "화면 전용 배포"를 하나 더 얹어, 짧은 HTTPS 주소를 얻는 것뿐입니다. 두 주소가 동시에 살아 있어도 괜찮습니다.

사전 조건

이 장은 1~20장의 결과물 위에서 시작합니다. 아래가 갖춰져 있어야 합니다. 하나라도 빠지면 중간에 막히니 먼저 확인하세요.

참고

도메인은 사지 않습니다 짧은 HTTPS 주소를 얻으려고 도메인을 따로 구매하지 않습니다. Vercel은 배포할 때마다 xxx.vercel.app 형태의 기본 주소를 무료 HTTPS로 자동 발급해 줍니다. 이 장은 그 기본 주소만으로 끝까지 갑니다.

21.1 정적 호스팅과 CDN — 화면은 왜 컨테이너 밖에 둘까

지금 우리 프론트는 web 컨테이너(Nginx)가 서빙합니다. 즉 화면 파일들이 ECS 컨테이너 안에 들어 있습니다. 잘 도는데, 왜 굳이 밖으로 뺄까요? 먼저 개념부터 정리합시다.

용어

정적 파일(Static Files) React를 빌드하면 나오는 결과물은 index.html, 자바스크립트 묶음(.js), 스타일(.css), 이미지 같은 "이미 완성돼 바뀌지 않는 파일"들입니다. 서버가 매번 계산해서 만들어 내는 게 아니라, 그냥 그대로 내려 주기만 하면 되는 파일이라 "정적(static)"이라고 부릅니다. 우리 프론트가 바로 이겁니다.

용어

정적 호스팅(Static Hosting) 이런 정적 파일들을 올려 두면, 접속하는 사람에게 그대로 내려 주는 전용 서비스입니다. 서버를 직접 돌보지 않아도 되고, 파일만 올리면 됩니다. Vercel, Netlify, GitHub Pages, AWS S3+CloudFront 등이 여기에 해당합니다.

용어

CDN(Content Delivery Network) 전 세계 곳곳에 파일 사본을 미리 복제해 두는 배달망입니다. 서울 사용자는 서울 근처 서버에서, 뉴욕 사용자는 뉴욕 근처 서버에서 화면 파일을 받아, 어디서 접속하든 빠릅니다. 정적 호스팅 서비스는 대부분 이 CDN을 기본으로 깔고 있습니다.

용어

Vercel(버셀) 정적 프론트(그리고 일부 서버 기능)를 GitHub 저장소와 연결해 손쉽게 배포해 주는 클라우드 서비스입니다. GitHub에 코드를 올리면 자동으로 빌드해서 CDN에 올리고, xxx.vercel.app 주소와 무료 HTTPS를 붙여 줍니다. React를 만든 Meta처럼, Vercel은 Next.js를 만든 회사이기도 합니다.

[활용 사례] 정적 호스팅과 Vercel은 실무에서 어디에 쓰이나

화면을 컨테이너나 서버가 아니라 이런 정적 호스팅에 두는 방식은, 요즘 웹 서비스에서 아주 흔한 표준입니다.

[맛보기] 아주 작은 정적 HTML 하나를 Vercel에 올려 보기

우리 프로젝트를 건드리기 전에, "정적 파일을 올리면 HTTPS 주소가 생긴다"를 아주 작은 예로 먼저 손으로 느껴 봅시다. 이 맛보기는 우리 투표 서비스와 완전히 분리된, 버리는 연습입니다. HTML 딱 한 장을 Vercel에 올려 봅니다.

먼저 연습용 파일을 만듭니다. VS Code에서 vote-app 폴더를 열고, 터미널 (vote-app에서)에서 claude를 실행한 뒤 아래 프롬프트를 그대로 넣으세요. 우리 프로젝트 폴더와 섞이지 않게, 문서 폴더 안에 별도 폴더로 만듭니다.

프롬프트

(AI 입력창)

Vercel 배포 연습을 하려고 해. 우리 vote-app 프로젝트와 완전히 분리된
연습용으로, 문서(Documents) 폴더 안에 hello-vercel 이라는 새 폴더를
만들고, 그 안에 index.html 한 개만 넣어 줘. 내용은 "안녕하세요, 첫
Vercel 배포입니다" 라는 큰 제목이 보이는 아주 단순한 HTML이면 돼.
그리고 그 hello-vercel 폴더를 내 GitHub의 새 비공개 저장소
hello-vercel 로 올려 줘. (우리 vote-app 저장소와 섞지 말고 별도로.)

Claude Code가 폴더와 index.html을 만들고, hello-vercel이라는 새 저장소로 올려 줍니다. 중간에 승인을 물으면 내용을 보고 승인하세요. 이제 이걸 Vercel에 올립니다. (계정이 없다면 바로 다음 21.2에서 만드는 계정으로 하면 됩니다. 계정을 먼저 만들고 이 맛보기로 돌아와도 됩니다.)

  1. 웹브라우저에서 https://vercel.com 에 접속해, GitHub 계정으로 로그인 합니다. (계정 만들기·GitHub 연결은 21.2에서 자세히 다룹니다.)
  2. 로그인 후 나오는 대시보드에서 Add New…(새로 추가) → Project(프로젝트) 를 누릅니다.
  3. GitHub 저장소 목록에서 방금 만든 hello-vercel 을 찾아 그 옆의 Import(가져오기) 를 누릅니다.
    • [참고] 목록에 안 보이면, 목록 위의 Adjust GitHub App Permissions(GitHub 권한 조정) 같은 링크를 눌러 Vercel이 이 저장소를 볼 수 있게 허용해 줍니다. (21.3에서 다시 설명합니다.)
  4. 설정 화면이 나오면 아무것도 바꾸지 말고 그대로 Deploy(배포) 를 누릅니다. (HTML 한 장이라 특별한 설정이 필요 없습니다.)
  5. 잠시 기다리면 축하 화면과 함께 https://hello-vercel-xxxx.vercel.app 같은 주소가 나옵니다. 그 주소를 눌러 열면, "안녕하세요, 첫 Vercel 배포입니다" 화면이 https(자물쇠 표시) 로 뜹니다.

방금 여러분은 정적 파일을 Vercel에 올려, 무료 HTTPS 주소로 여는 것을 직접 해 봤습니다. 이게 이 장에서 우리 프론트에 할 일의 축소판입니다. 이 연습용 hello-vercel은 이제 필요 없으니, 나중에 지워도 되고 그냥 둬도 됩니다.

21.2 Vercel 계정 만들기

이제 본격적으로 시작합니다. 먼저 Vercel 계정을 만듭니다. 우리 코드가 GitHub에 있으니, GitHub로 로그인하는 것이 가장 매끄럽습니다.

  1. 웹브라우저에서 https://vercel.com 에 접속합니다.
  2. 오른쪽 위 Sign Up(가입) 을 누릅니다. (이미 계정이 있으면 Log In 으로 로그인하고 이 절을 건너뜁니다.)
  3. 가입 방식 선택 화면에서 Continue with GitHub(GitHub로 계속) 를 누릅니다. 1장에서 만든 그 GitHub 계정을 씁니다.
  4. GitHub 로그인 창이 뜨면 로그인하고, Vercel이 GitHub에 접근하는 것을 허용(Authorize)합니다.
  5. 이름·용도 같은 걸 물으면 개인용(Hobby / Personal)으로 진행합니다. 이 실습은 무료(Hobby) 플랜으로 충분하니, 카드 정보를 넣으라는 화면이 나와도 유료로 넘어가지 않아도 됩니다.
확인

로그인이 끝나면 Vercel 대시보드(Dashboard) 화면이 보입니다. 화면 오른쪽 위에 내 계정 아이콘이 있으면 로그인된 상태입니다. 아직 프로젝트가 없으면 목록이 비어 있는데, 정상입니다.

참고

Vercel과 GitHub를 연결한다는 것 GitHub로 로그인하면, Vercel이 "내 GitHub 저장소를 읽고 배포할 수 있는" 권한을 받습니다. 그래서 다음 절에서 저장소 목록이 Vercel 안에 바로 나타납니다. 어떤 저장소까지 보여 줄지는 21.3에서 고를 수 있습니다.

21.3 우리 프론트를 Vercel에 배포하기

이제 vote-app 저장소의 frontend 폴더를 Vercel에 올립니다. 핵심은 Root Directory(루트 디렉터리)를 frontend로 지정하는 것 하나입니다. 우리 저장소는 frontendbackend가 한 저장소에 같이 있으므로, "이 중 frontend만 배포 대상"이라고 알려 줘야 하기 때문입니다.

[콘솔] 1단계: 저장소 가져오기(Import)

  1. Vercel 대시보드에서 Add New…(새로 추가) → Project(프로젝트) 를 누릅니다.
  2. Import Git Repository(깃 저장소 가져오기) 목록에서 vote-app 을 찾습니다.
    • [주의] 목록에 vote-app이 안 보이면 — 우리 저장소는 비공개라, Vercel에 아직 볼 권한을 안 줬을 수 있습니다. 목록 근처의 Adjust GitHub App Permissions(또는 "Configure GitHub App", "GitHub 권한 조정") 링크를 누르고, 열린 GitHub 화면에서 All repositories(모든 저장소) 또는 Only select repositories → vote-app 선택 을 골라 저장(Save) 합니다. 그러면 Vercel 목록에 vote-app이 나타납니다.
  3. vote-appImport(가져오기) 를 누릅니다.

[콘솔] 2단계: Root Directory를 frontend로 지정하고 배포

가져오면 프로젝트 설정 화면이 나옵니다. 여기서 딱 한 곳을 바꿉니다.

  1. Root Directory(루트 디렉터리) 항목을 찾아 Edit(편집) 를 누릅니다.
  2. 폴더 목록에서 frontend 를 선택합니다. (Root Directory 값이 frontend로 바뀝니다.)
    • [주의] 여기를 지정하지 않으면 Vercel이 저장소 맨 바깥(backend가 섞인 곳)을 빌드하려다 실패합니다. 반드시 frontend로 지정하세요. 이 장에서 가장 많이 틀리는 지점입니다.
  3. Framework Preset(프레임워크 프리셋) 을 확인합니다. frontend가 Vite로 만든 React 프로젝트이므로, Vercel이 자동으로 Vite 로 감지해 놓았을 겁니다. 그대로 둡니다. (빌드 명령·출력 폴더도 Vite 기본값으로 자동으로 채워집니다. 직접 건드릴 필요 없습니다.)
    • [참고] 자동 감지가 Vite가 아니라 Other로 잡혀 있으면, 드롭다운에서 Vite 를 직접 골라 줍니다.
  4. Environment Variables(환경 변수) 는 지금은 건드리지 않습니다. 우리 프론트는 API를 상대 경로 /api로 부르므로, 화면 쪽에 넣을 비밀값이 없습니다. (API 연결은 다음 절 rewrites로 합니다.)
  5. Deploy(배포) 를 누릅니다. 빌드 로그가 흐르고, 1~2분 뒤 축하 화면과 함께 https://vote-app-xxxx.vercel.app 같은 주소가 나옵니다.
확인

나온 주소를 눌러 열어 보세요. 투표 화면 자체는 HTTPS로 뜹니다. 다만 지금은 아직 투표가 되지 않거나 설문이 안 불러와질 수 있습니다. 정상입니다. 화면(/)은 Vercel이 서빙하지만, 데이터 (/api/...)를 어디로 보낼지 아직 안 알려 줬기 때문입니다. 그 연결을 다음 절에서 vercel.json으로 합니다.

21.4 rewrites — Vercel이 대신 ALB로 넘겨 준다

화면은 떴는데 데이터가 안 옵니다. 프론트가 /api/polls 같은 요청을 보내면, 지금은 Vercel 자기 자신에게 물어보는 꼴이라 그런 데이터가 없다고 나옵니다. 이 /api 요청을 우리 ALB로 넘겨 줘야 합니다. 이때 쓰는 것이 Vercel의 rewrites입니다.

용어

rewrites(리라이트) — 서버측 프록시 "이 경로로 오는 요청은 내가 대신 저쪽으로 넘겨서 받아다 줄게"라고 Vercel에게 시키는 규칙입니다. 브라우저가 직접 저쪽(ALB)으로 가는 게 아니라, Vercel 서버가 대신 저쪽에 요청을 보내 응답을 받아 브라우저에 돌려줍니다. 브라우저 입장에서는 계속 Vercel 주소하고만 대화합니다.

이 발상, 사실 여러분은 두 번이나 봤습니다.

Vercel rewrites는 같은 아이디어의 배포판입니다. 이번엔 Vercel 서버가 /api를 우리 ALB로 넘겨 줍니다. 그래서 화면 코드는 4장에서 정한 대로 /api만 부르면 되고, 한 줄도 안 고쳐도 됩니다.

왜 이게 CORS·혼합 콘텐츠를 피하나. 두 용어를 다시 짚고 갑니다.

용어

CORS (교차 출처 요청 차단) — 4장 복습 브라우저의 보안 규칙입니다. 화면 주소(Vercel, https)와 데이터 요청 주소가 서로 다른 출처면 브라우저가 요청을 막습니다. 만약 프론트가 브라우저에서 직접 ALB로 요청했다면 출처가 달라 CORS에 걸립니다.

용어

혼합 콘텐츠(Mixed Content) HTTPS(자물쇠) 페이지 안에서 HTTP(자물쇠 없는) 주소로 요청을 보내면, 브라우저가 "안전한 페이지가 안전하지 않은 걸 부른다"며 그 요청을 막거나 경고합니다. 우리 ALB는 HTTP라, 만약 브라우저가 직접 ALB(http)를 불렀다면 이 혼합 콘텐츠 문제에 걸립니다.

rewrites는 이 둘을 한 번에 피합니다. 브라우저는 오직 Vercel(https, 같은 출처)하고만 통신합니다. ALB(http)로의 요청은 브라우저가 아니라 Vercel 서버가 뒤에서 대신 보냅니다. 브라우저는 그 HTTP 통신을 보지도 못하므로, CORS도 혼합 콘텐츠도 생길 수가 없습니다.

[활용 사례] rewrites(리라이트 프록시)는 실무에서 어디에 쓰이나

[우리 프로젝트] vercel.json에 rewrites 넣기

이제 frontend 폴더에 vercel.json 파일을 만들어, /api로 오는 요청을 우리 ALB로 넘기는 규칙을 씁니다. 이 파일도 Claude Code에게 맡깁니다.

먼저 내 ALB DNS 주소를 손에 준비하세요(사전 조건에서 확인한 값). 예를 들어 vote-alb-1234567890.ap-northeast-2.elb.amazonaws.com 라고 합시다. 여러분의 실제 주소로 바꿔서 쓰면 됩니다.

VS Code에서 vote-app 폴더를 연 상태로, 터미널(vote-app)에서 claude를 실행한 뒤 아래 프롬프트를 넣으세요. 꺾쇠(<>) 부분을 내 실제 ALB DNS 주소로 바꾸는 것을 잊지 마세요.

프롬프트

(AI 입력창)

frontend 폴더 안에 vercel.json 파일을 만들어 줘. 목적은 이거야:
프론트를 Vercel에 배포했는데, /api 로 오는 요청을 우리 AWS ALB로
서버측에서 넘기고 싶어(rewrites 사용).
ALB 주소는 http://<내-ALB-DNS-주소> 이고, 우리 API도 /api 경로를 써.
그러니까 /api/:path* 로 오는 요청을
http://<내-ALB-DNS-주소>/api/:path* 로 rewrite 되게 vercel.json을 만들어 줘.
만든 다음, 이 변경을 git으로 커밋하고 GitHub(vote-app 저장소)에 push까지 해 줘.

Claude Code가 만들어 줄 frontend/vercel.json은 아래와 같은 모습이어야 합니다. 여러분의 ALB 주소가 destination에 정확히 들어갔는지 눈으로 확인하세요.

{
  "rewrites": [
    {
      "source": "/api/:path*",
      "destination": "http://vote-alb-1234567890.ap-northeast-2.elb.amazonaws.com/api/:path*"
    }
  ]
}
주의

destination이 http://인데 괜찮은가요? 괜찮습니다. 이 HTTP 요청은 Vercel 서버가 뒤에서 보내는 것이라, 브라우저의 혼합 콘텐츠 규칙과 무관합니다. 브라우저는 여전히 HTTPS Vercel하고만 통신합니다. destination에는 우리 ALB가 HTTP만 열려 있으니 http://가 맞습니다. 뒤에 슬래시나 경로를 빠뜨리지 말고, .../api/:path* 까지 정확히 넣으세요.

주의

ALB 주소 오타를 조심하세요 destination의 주소에 오타가 하나라도 있으면 /api 요청이 엉뚱한 곳으로 가 투표가 안 됩니다. 14장에서 복사해 둔 주소를 그대로 붙여넣는 게 안전합니다. 앞에 http://가 붙어 있는지, 끝이 /api/:path*인지 확인하세요.

21.5 git push로 자동 재배포하기

vercel.json을 방금 GitHub에 push했습니다. 그러면 Vercel이 알아서 다시 배포합니다. 21.3에서 저장소를 연결해 둔 순간부터, 이 저장소(정확히는 frontend)에 코드가 올라올 때마다 Vercel이 자동으로 새로 빌드해 올리기 때문입니다. 우리가 3부에서 GitHub Actions로 만든 자동 배포와 같은 일이, 프론트에서는 설정 없이 기본으로 일어납니다.

  1. 웹브라우저에서 Vercel 대시보드 → 우리 프로젝트(vote-app)로 들어갑니다.
  2. Deployments(배포 내역) 탭을 보면, 방금 push로 새 배포가 Building (빌드 중)Ready(준비됨) 로 바뀌는 것이 보입니다. 1~2분이면 끝납니다.
    • [참고] 자동으로 안 도는 것 같으면, 21.4의 프롬프트에서 push가 실제로 됐는지 확인하세요. GitHub의 vote-app 저장소 frontend 폴더에 vercel.json이 올라와 있어야 합니다. 없으면 Claude Code에게 "frontend의 vercel.json을 커밋하고 push해 줘"라고 다시 요청합니다.
  3. 배포가 Ready가 되면, 프로젝트 화면 위쪽의 Domains(도메인) 또는 Visit(방문) 버튼에 있는 https://vote-app-xxxx.vercel.app 주소를 눌러 엽니다.

이제 그 HTTPS 주소에서 설문이 뜨고 투표가 되면, 뒤에서 이런 일이 벌어진 것입니다: 브라우저가 Vercel(https)에서 화면을 받고, 투표할 때 보낸 /api 요청을 Vercel 서버가 rewrite로 우리 ALB(http)에 대신 넘겼고, ALB → ECS(api 컨테이너) → RDS로 이어져 결과가 돌아왔습니다.

[확인]

이번 장이 제대로 끝났는지 눈으로 확인합니다.

  1. Vercel이 준 https://...vercel.app 주소로 접속하면, 주소창에 자물쇠(HTTPS) 가 보이고 투표 화면이 뜬다.
  2. 14장에서 심어 둔 설문(예: "점심 뭐 먹지?")이 보이고, 선택지를 눌러 투표하면 득표수가 늘어난다. (14장과 같은 RDS 데이터를 쓰므로 같은 설문이 보입니다.)
  3. 브라우저에서 개발자 도구 → Network(네트워크) 탭을 연 채 투표를 한 번 해 봅니다. /api/... 요청이 목록에 뜨는데, 그 요청의 도메인이 내 Vercel 주소(https) 로 나갑니다. (ALB의 http 주소가 브라우저에 직접 보이지 않아야 정상입니다. ALB로의 연결은 Vercel 서버 뒤에서 일어나기 때문입니다.)
  4. 콘솔(Console) 탭에 CORSMixed Content(혼합 콘텐츠) 오류가 없다.

이 네 가지가 보이면, 프론트를 Vercel로 분리해 HTTPS로 배포하고 API는 기존 ECS·RDS를 그대로 쓰는 심화 완성에 도착한 것입니다. 이 짧은 HTTPS 주소를 친구에게 보내 보세요.

확인

개발자 도구 여는 법 크롬·엣지에서 화면 위 아무 곳이나 마우스 오른쪽 클릭 → 검사(Inspect), 또는 F12(macOS는 Command + Option + I)를 누르면 개발자 도구가 열립니다. 위 탭에서 Network를 고른 뒤 페이지를 새로고침하고 투표해 보세요.

막히면

이 장에서 자주 나는 오류와 대처법입니다.

[체크리스트]

아래가 모두 됐는지 확인해 보세요. 하나라도 안 됐다면 해당 절로 돌아가 다시 진행합니다.

모두 체크됐다면, 기본 완성(ECS+ALB) 위에 실무에서 흔한 "프론트 분리 배포 + HTTPS"까지 얹은 것입니다. 14장의 ALB 주소와 이번 Vercel 주소가 둘 다 살아 있고, 둘 다 같은 API·DB를 씁니다. (전체 여정에서 지금 어디쯤인지는 맨 앞 [지금 여기] 지도로 확인하세요.)