MakeAI 딥러닝

이 웹사이트를 AWS에 올리면서 배운 것 — EC2 한 대, Caddy, Docker Compose

거창한 인프라 없이 EC2 한 대에 Caddy와 Docker Compose로 Next.js + NestJS + PostgreSQL을 올렸어요. CDK로 재현 가능하게 만들면서 겪은 자잘한 함정들이에요.

3분 읽기

이번 주는 가벼운 글이에요. 지금 보고 계신 이 사이트(makeai.biz)를 AWS에 올리면서 겪은 것들이요. 회사 소개 사이트라 트래픽이 크지 않아서, EC2 한 대로 단순하게 갔어요. 그래도 재현 가능하고 다시 배포하기 쉽게 만드는 데는 신경을 썼어요.

구성

인터넷 → Route53 → EIP → EC2 (t3.small)
                           └ docker compose
                               ├ caddy   (자동 HTTPS, 여러 도메인)
                               ├ web     (Next.js standalone)
                               ├ api     (NestJS)
                               └ postgres
  • 프런트는 Next.js standalone 빌드, API는 NestJS, DB는 PostgreSQL. 전부 컨테이너예요.
  • 앞단은 Caddy예요. 도메인 목록만 주면 인증서를 알아서 받고 갱신해요. nginx + certbot 조합보다 설정이 열 줄은 짧아요.
  • 인프라는 AWS CDK(TypeScript)로 정의했어요. 이미지 빌드·ECR 푸시·인스턴스·보안그룹·IAM이 코드 하나에 있어요.
  • 배포는 스크립트 하나예요. CDK 배포 → SSM으로 인스턴스에 "업데이트 스크립트 실행" 명령 → 인스턴스가 새 이미지를 받아서 compose up.

SSH는 안 열었어요. 보안그룹에 80·443만 있고, 서버 접속은 SSM Session Manager로 해요. 키 관리가 없어져서 편해요.

함정 1. 탄력적 IP 한도

처음 배포에서 EIP 생성이 "주소 한도 초과"로 실패했어요. 계정에 안 쓰는 EIP가 쌓여 있었던 거예요. 급한 대로 인스턴스가 부팅할 때 자기 공인 IP를 Route53에 직접 등록하는 스크립트를 넣어서 EIP 없이 돌렸어요. 나중에 한도를 올리고 EIP를 붙였는데, 이 자가 등록 스크립트는 그대로 뒀어요. 인스턴스가 바뀌어도 DNS가 따라오니까요. IAM은 그 도메인의 A 레코드만 바꿀 수 있게 좁혔어요.

함정 2. 시작 템플릿 이름 충돌

같은 계정에 CDK 스택이 하나 더 있었는데, 둘 다 기본 이름의 시작 템플릿을 만들려고 해서 충돌했어요. CDK 기능 플래그 하나로 템플릿 이름을 고유하게 만들면 해결돼요. 실패한 스택이 ROLLBACK_COMPLETE 상태로 남으면 지우고 다시 배포해야 하는 것도 이때 배웠어요.

함정 3. Windows 셸과 슬래시

로컬이 Windows라서 Git Bash에서 AWS CLI를 썼는데, /aws/... 로 시작하는 SSM 파라미터 경로를 Git Bash가 Windows 경로로 바꿔 버렸어요. PowerShell에서 하거나 경로 변환을 끄면 돼요. 알고 나면 별거 아닌데 한참 헤맸어요.

함정 4. 빌드 시점에 고정되는 것들

Next.js의 basePath, rewrites, NEXT_PUBLIC_* 환경 변수는 빌드할 때 박혀요. 런타임에 바꿀 수 없어요. 개발 환경은 하위 경로에, 운영은 루트 도메인에 올리는데 이걸 빌드 인자로 넘기는 걸 처음에 놓쳤어요. Dockerfile에 ARG로 명시하고 CDK에서 넘기도록 했어요.

함정 5. 프록시 뒤의 클라이언트 IP

문의 폼에 IP 기준 요청 제한이 있는데, Caddy 뒤에서는 모든 요청이 Caddy의 IP로 보여요. X-Forwarded-For를 읽어야 해요. 이건 아직 고치는 중이에요. 솔직히 쓰면 지금은 모든 방문자가 한 IP로 세어져요. 트래픽이 작아서 문제는 안 됐지만 알고는 있어요.

비밀 값 관리

DB 비밀번호와 관리자 토큰은 Secrets Manager에, 이미지 태그와 도메인 목록 같은 설정은 SSM Parameter Store에 뒀어요. 인스턴스의 업데이트 스크립트가 부팅 때와 배포 때 이걸 읽어서 .env를 만들어요. 저장소에는 비밀 값이 하나도 없어요. 인스턴스 역할은 이 비밀 값들과 ECR 읽기, 자기 도메인 DNS 쓰기만 할 수 있어요.

아직 없는 것

  • DB 백업. 문의 내역이 인스턴스 볼륨에만 있어요. S3로 주기 백업을 붙여야 해요.
  • 무중단 배포. compose up 하는 몇 초 동안 끊겨요. 회사 소개 사이트라 감수했어요.
  • 모니터링. CloudWatch 기본 지표만 있어요.

작은 사이트에 큰 인프라를 붙이는 것보다, 작게 가되 재현 가능하게 만드는 게 저희한테는 맞았어요. 전부 코드에 있으니 나중에 커지면 그때 바꾸면 돼요.

다음 주는 이 사이트에서 연결되는 공개 데모를 어떻게 안전하게 운영하는지예요. 기본 인증(Basic auth)으로 데모 계정을 돌리면서 챙긴 것들이요.