클라우드 에뮬레이터 스택 — 4번째 환경의 설계
> 아래 다중 에뮬레이터 경로와 명령은 독립 스택 설계를 설명하는 예시입니다. 실제 저장소에 적용할 때는 필요한 공급자와 비용을 먼저 선택하세요.
목차
아래 다중 에뮬레이터 경로와 명령은 독립 스택 설계를 설명하는 예시입니다. 실제 저장소에 적용할 때는 필요한 공급자와 비용을 먼저 선택하세요.
대부분의 모노레포는 local / dev / prod 라는 익숙한 3단 구성. 이 셋의 공통점은 앱과 인프라가 한 묶음. local 에서 띄우는 Postgres · Redis 도, prod 의 그것도 "우리가 운영하는 인프라" 의 일부. 외부 클라우드 (Supabase · AWS) 에 의존하는 시점이 오면 이 3단 구성이 쪼개집니다 — 우리 인프라가 아닌 남의 인프라가 끼어들기 때문. 이 글은 그 상황에서 4번째 환경으로 cloud 를 도입한 설계.
1. 무엇이 문제인가
외부 클라우드를 직접 호출하는 코드가 늘어나면 다음 비용이 일상화:
- 인터넷 의존 — 비행기 · 지하철 · 해외 출장에서 손이 멈춤.
- 할당량 / 비용 — 무료 티어가 빠르게 마름. CI 가 매번 호출하면 비용 누적.
- 공유 자원의 오염 — 한 명이 만든 테스트 데이터가 다른 사람의 화면에 뜸.
- 시크릿 노출 — 로컬
.env에 실 운영 키가 박히면 깃 히스토리에 영원히 남을 위험.
해결은 두 갈래.
- 모킹 — SDK 호출을 jest mock 으로 가림. 코드 행위는 빠르게 검증되지만 실제 SDK · HTTP 흐름이 생략되어 통합 시점에 문제가 터짐.
- 로컬 에뮬레이터 — Supabase · LocalStack · MiniStack 같은 프로세스를 로컬에 띄우고 endpoint 만 바꿈. SDK · HTTP · 인증 흐름까지 그대로 검증. 비용은 컨테이너 자원.
cloud 환경은 두 번째 길의 보관 장소.
2. 별 환경으로 분리
기존 LOCAL / DEV / PROD 의 compose 와 같은 폴더에 두지 않음. infra/cloud/ 라는 네 번째 폴더를 신설:
- 외부 클라우드 에뮬레이터는 켜고 끄는 단위가 다름. local 인프라 (DB · Redis) 는 매일 켜지만, AWS 에뮬레이터는 SES 메일 검증할 때만 띄움.
- 포트 대역도 다름. LOCAL / DEV / PROD 는 표준 + 1만 + 2만 규칙을 따르지만, AWS 에뮬레이터는 4566 같은 도구별 디폴트 포트를 그대로 받는 게 호환성 측면에서 유리.
- 운영 배포 대상이 아님. PROD 에는 진짜 클라우드.
3. 각 에뮬레이터를 독립 compose 로
infra/cloud/
├── supabase/
│ └── docker-compose.yml # 14 컨테이너 (Auth · REST · Storage · Realtime · Studio · ...)
├── ministack/
│ └── docker-compose.yml # MiniStack + 동반 Redis
├── localstack/
│ └── docker-compose.yml # LocalStack 단독
├── firebase/
│ └── docker-compose.yml # Firebase Local Emulator (Auth · Firestore · RealtimeDB · Storage · Pub/Sub)
└── wiremock/
└── docker-compose.yml # 임의 외부 HTTP API stub
다섯 개를 한 compose 로 합치지 않는 이유는 선택적 기동. Supabase Auth 검증할 때는 supabase 만, AWS S3 멀티파트 PoC 할 때는 ministack 만 띄우면 메모리 · 시작 시간 절약. compose 의 profiles: 로도 가능하지만 디렉터리 분리가 README · 환경변수 · 트러블슈팅 동선에서 더 깔끔.
cd infra/cloud/supabase && docker compose up -d # 필요한 것만
cd infra/cloud/ministack && docker compose up -d
4. 포트는 도구의 디폴트를 존중
LocalStack · MiniStack 둘 다 4566 이 디폴트. boto3 · Terraform · @aws-sdk 모두 이 포트를 가정해 만든 예제 · 튜토리얼이 인터넷에 깔려 있음. 한쪽을 LOCAL / DEV 의 1만 / 2만 규칙에 맞추면 외부 자료를 쓸 때마다 매번 변환해야 함.
대신 두 에뮬레이터를 동시에 띄울 때만 한쪽을 시프트. 한 프로젝트 셋업은 LocalStack 만 호스트 포트를 4666 으로 옮김 (컨테이너 내부 4566 은 그대로 — SDK 호환 유지).
| 도구 | 호스트 포트 | 컨테이너 내부 |
|---|---|---|
| MiniStack | 4566 | 4566 |
| LocalStack | 4666 | 4566 |
Supabase 는 54320 대역 (Kong / DB / Studio / Inbucket) 을 묶어서 사용. supabase CLI 의 디폴트와 동일.
5. LOCAL / DEV / PROD 와 직교성 보장
cloud 의 어떤 포트도 LOCAL / DEV / PROD 의 포트와 충돌하면 안 됨. 그래야 사용자가 infra/local/docker-compose.yml 의 DB · Redis 와 infra/cloud/ministack/docker-compose.yml 의 MiniStack 을 동시에 띄울 수 있음.
여기서 한 가지 함정 — MiniStack 은 동반 Redis 가 필수이고 디폴트 6379 노출. local 의 Redis 도 6379. 충돌. 해결은 단순 — MiniStack 의 동반 Redis 를 호스트에 expose 안 함. 그건 MiniStack 컨테이너 내부 통신용이지 앱이 직접 붙을 대상이 아님.
ministack-redis:
image: redis:7-alpine
# ports 절 없음 — 컨테이너 네트워크 내부에서만 접근
6. 호스트를 가로지르는 안티패턴
cloud 에뮬레이터들은 각자 별 docker-compose 의 별 네트워크에 살아 있음. 이걸 호스트 도커 소켓 마운트 한두 개로 연결하면 안 됨. 예를 들어 Supabase Storage 가 MiniStack 의 S3 를 백엔드로 쓰게 만드는 건 가능하지만, 두 compose 가 동시에 떠 있을 때만 동작. 사용자가 ministack 만 끄면 supabase 가 바로 깨짐.
대신 각 스택은 자체 완결:
- Supabase Storage 는
STORAGE_BACKEND=file— 자체 디스크에 저장. - MiniStack 의 S3 가 필요한 코드는
endpoint=http://localhost:4566으로 직접 호출.
서로의 의존을 끊어야 "필요한 만큼만 띄운다" 가 약속이 됩니다.
7. 시크릿 다루기
cloud 에뮬레이터의 시크릿 (예: Supabase 의 JWT_SECRET · ANON_KEY) 은 로컬 전용 데모 키. 운영 키가 아님. 그래서 .env.example 에 기본값을 박아두는 것이 안전 — 로테이션 의무도 없고 노출 위험도 없음.
대신 한 가지를 지킴 — 운영 시크릿이 cloud 의 .env 에 섞이지 않도록 .env 자체를 git 무시. 누군가 실수로 운영 키를 같은 파일에 적어 commit 하는 사고를 막음.
8. 다른 환경과 조합
| 시나리오 | 함께 띄우는 것 |
|---|---|
| 단순 로컬 개발 | LOCAL (DB · Redis) + 호스트의 pnpm dev |
| 오프라인 개발 (외부 Supabase 차단) | LOCAL + cloud/supabase |
| AWS S3 멀티파트 PoC | cloud/ministack 만 (LOCAL 불필요) |
| SES 메일 라우팅 검증 | cloud/localstack 만 |
| Kakao Login · FCM · Gemini stub | cloud/wiremock 만 |
| Firebase Auth · Firestore 검증 | cloud/firebase 만 |
| CI 에서 통합 테스트 | LOCAL + cloud/{supabase,localstack} (필요한 것만) |
원칙은 단순 — 필요한 환경의 합집합. 4단 구성으로 옮기면서 잃은 건 없고 얻은 건 켜고 끄는 단위의 자유.
9. 한계
- 메모리 발자국 — 다섯 다 띄우면 idle ~3.5 GB. Supabase 풀 셋업이 가장 무거움 (~3 GB). 노트북 사양에 따라 항상 켜둘 수는 없음.
- 버전 핀 부담 — 에뮬레이터의 릴리스 주기가 빠름. 오래된 compose 를 그대로 두면 어느 날 갑자기 unhealthy 가 뜸. 정기적 새 태그 갱신.
- 한 머신 한 사용자 가정 — cloud 환경의 시크릿 · 데모 키는 단일 개발자 PC 가정. 팀이 공유 인프라로 쓰려면 별도 인증 · 분리.
- 에뮬레이터 ≠ 실제 클라우드 — 통합 테스트가 cloud 에서 통과해도 운영 직전에는 실제 클라우드에서 한 번 더 검증. cloud 환경은 빠른 검증 채널이지 최종 검증 채널이 아님.
- FCM 미포함 — Firebase Local Emulator 가 FCM 을 공식 지원하지 않음. 푸시는 WireMock stub 으로 우회.
하고픈 말
local / dev / prod 3단은 자체 인프라만 다루던 시대의 표준. 외부 클라우드 의존이 늘면 4번째 환경 cloud 가 자연스러운 답. 핵심은 각 에뮬레이터를 자체 완결 + 도구 디폴트 포트 존중 + 켜고 끄는 단위를 작게. 이 셋이 함께 있으면 비행기 안에서도 SDK 흐름을 그대로 시험할 수 있습니다.
Next
- (infra 끝)
Supabase self-hosted · LocalStack · MiniStack · Firebase Local Emulator · WireMock 를 참고합니다.
이 글에서 만나는 용어
infra 카테고리의 다른 글
카테고리 전체 보기 →관련 글
Firebase Local Emulator Suite — Firebase 한 묶음을 노트북에
Firebase 는 2011 년 작은 스타트업으로 시작해 2014 년 Google 에 인수됐습니다. 매니지드 모델이 강한 서비스답게 한동안은 "로컬에서 띄우는 길이 없다" 가 맞는 답이었지만, 2020 년 3 월 Firebase Local Emulator Suite 가 정식 GA 되면서 그림이 바뀌었습니다.
Supabase Self-Hosted — Postgres 한 통에 BaaS 를 담는 방법
Supabase 는 "오픈소스 Firebase" 라는 슬로건으로 2020 년 등장했습니다. Firebase 가 자체 NoSQL · 자체 Auth · 자체 Storage 처럼 하나의 거대한 매니지드 박스라면, Supabase 는 Postgres 를 중심에 두고 그 위에 작은 컴포넌트를 얹는 방식을 택했습니다. 매니지드 (…