Cloud 1단계 초기 배포 설계 - 100-hours-a-week/KTB4-5th-wiki GitHub Wiki

다먹자 v1 인프라 위키 — 1단계: EC2 + Docker Compose 기반 초기 배포 설계

KTB 4조 다먹자 팀의 v1 인프라 설계 문서입니다. 이 문서는 1단계(EC2 + Docker Compose 기반 초기 배포) 만 다룹니다. 2단계(CI)는 별도 문서 ktb4-cloud-wiki-ci.md를 참고하세요. 각 결정 항목은 상황 → 결론 → 이유 순서로 정리했습니다.

목차

  1. 클라우드 벤더 선택
  2. 초기 서비스 규모 정의
  3. Docker 컨테이너 구성 설계
  4. Docker Compose 기반 배포 구조 설계
  5. 현재 구조의 한계 정의

0. 클라우드 벤더 선택

  • 상황: 인프라를 구축할 클라우드 벤더(AWS, GCP, Azure 등)를 선택해야 함. KTB 부트캠프 교육 과정에서 이미 AWS를 다뤘고, v1 MVP를 빠르게 구축해야 하는 시간적 제약이 있음.
  • 결론: AWS 선택
  • 이유:
    1. 이미 학습한 경험이 있어 빠르게 적용 가능 — KTB 교육 과정에서 AWS를 배웠기 때문에 별도 학습 곡선 없이 바로 설계·구현에 들어갈 수 있음. GCP나 Azure를 새로 익히는 시간을 아낄 수 있어 v1처럼 빠른 구축이 목표인 단계에 유리함.

    2. 가격 차이가 크지 않음 — 비슷한 사양(2 vCPU, 소형 버스터블/공유 코어 인스턴스)을 서울 리전 기준으로 비교하면:

      클라우드 인스턴스 사양 리전 온디맨드 시간당 월 환산 (730h)
      AWS t4g.small 2 vCPU / 2 GiB, ARM(Graviton2) ap-northeast-2 (서울) $0.0208 $15.18
      GCP e2-small 2 vCPU / 2 GiB, x86(공유 코어) asia-northeast3 (서울) $0.0215 $15.70
      Azure B2pts_v2 2 vCPU / 1 GiB, ARM(Ampere Altra) 리전별 가격 비공개, 공개된 기준가 $0.0084 $6.13

      AWS와 GCP는 동일 사양(2 vCPU/2 GiB) 기준으로 서울 리전에서 월 $15~16 수준이라 차이가 3% 이내에 불과함 — 가격만으로는 어느 한쪽을 택할 결정적 이유가 되기 어려움. Azure의 B2pts_v2가 더 저렴해 보이지만 메모리가 절반(1GiB)이라 동일 조건 비교는 아니며, Korea Central 리전별 정확한 가격은 공개된 페이지에서 확인하지 못함(위 표의 Azure 가격은 지역 미지정 공개 기준가). 따라서 비용보다는 이미 학습해 빠르게 적용할 수 있다는 점이 실질적인 선택 기준이 됨.


1. 초기 서비스 규모 정의

MAU

  • 상황: v1은 KTB 부트캠프 데모/초기 서비스로, 훈련생 그룹과 소수 외부 사용자만 접근할 것으로 예상됨.
  • 결론: MAU 200명
  • 이유: KTB 훈련생 130명 + KTB 관계자 20명 + 외부 사용자 50명

DAU

  • 상황: 알림을 받는 사용자와 실제로 앱을 여는 사용자가 다를 수 있어, DAU를 어떤 기준으로 잡을지 정해야 함.
  • 결론: DAU 140명
  • 이유: 유통기한 임박 알림을 확인하는 사용자만 실질적 활성 사용자로 봄. 이 사용자들은 냉장고에 유통기한 3일 이하 재료를 보유한 사람들이라 요리할 가능성이 높고, 그대로 집에서 식사할 가능성도 높다고 판단해 알림 확인자 수(140명)를 DAU로 설정.

사용자 흐름 및 Peak Traffic (RPS)

  • 상황: 인프라 사이징을 위해 시간대별 접근 패턴과 피크 트래픽(RPS)을 추정해야 함. v1 사용자 흐름은 로그인 → 냉장고 재고 확인 → 유통기한순 재정렬 조회.

  • 결론:

    시간대 접근자 수 윈도우 RPS
    평일 아침 8시 (알림 버스트) 140명 5분 1.4 RPS
    평일 점심 0명 – 0 RPS
    평일 저녁 100명 1시간 0.083 RPS
    주말 점심 150명 1시간 0.125 RPS
    주말 저녁 150명 1시간 0.125 RPS

    → 목표: 7 RPS (예상 RPS의 3~5배 여유 적용)

  • 이유: 아침 8시 알림 발송 직후 70%가 확인 접근(140명, 5분 윈도우)한다고 가정한 구간이 가장 뾰족한 피크. 평일 점심은 대부분 외부 식사라 접근 없음, 평일 저녁은 사용자 절반이 귀가(100명). 주말은 수강생 약 20명이 점심·저녁 각각 외식하고 나머지 150명은 집에서 식사한다고 가정 — 인원은 더 많아도(150명) 윈도우가 1시간으로 넓어 RPS 부담은 적음. 실제 흐름이 예상과 다를 수 있어 3~5배 여유를 둠.

Latency

  • 상황: 목표 응답속도 기준이 필요함.
  • 결론: 200ms(목표) ~ 1000ms(최대 허용)
  • 이유: 구글이 제시하는 응답시간 200ms를 목표치로 삼되, 1000ms까지는 최대 허용치로 설정.

가용성

  • 상황: 서비스 특성상 요구되는 가용성 수준을 정해야 함.
  • 결론: 99%
  • 이유: 서비스에 장애가 나도 사용자가 냉장고 속 재료를 육안으로 보며 요리할 수 있어 약간의 불편함에 그침 — 높은 가용성(99.9%+)이 필요하지 않다고 판단.

배포 빈도 / 전략

  • 상황: 스타트업 특성상 기능 개발과 배포가 잦을 것으로 예상되며, dev·prod 각각에 맞는 배포 방식이 필요함.
  • 결론: dev는 수시 배포(안정성보다 속도 우선), prod는 롤링 배포
  • 이유: 사용자 접근이 대부분 식사시간에 몰려 있어 새벽 시간대 빅뱅배포도 고려했으나, 야식을 먹는 사용자가 있다면 불편을 겪을 수 있어 이보다 무중단에 가까운 롤링 배포를 선택.

서버 인스턴스 — 모델 계열

  • 상황: MVP 인프라에 맞는 EC2 인스턴스 계열을 선택해야 함.
  • 결론: 범용 T 시리즈 (G/P 계열 제외)
  • 이유: MVP 구현 단계라 특정 워크로드에 특화된 인스턴스가 필요 없고, AI 파트 팀원에게 확인한 결과 v1 기능에는 GPU가 필요 없다고 판단됨.

서버 인스턴스 — ARM vs x86

  • 상황: 동일 성능 대비 비용과 호환성 사이의 트레이드오프를 검토해야 함.
  • 결론: ARM(t4g) 선택
  • 이유: 동일 성능일 때 ARM이 x86보다 저비용·저전력. MVP 단계에서는 사용할 라이브러리 중 ARM에 호환 안 되는 것이 없어 x86의 높은 호환성이 아직 필요하지 않음. 추후 x86 전용 라이브러리가 필요해져 인프라를 전환하더라도, 변경할 EC2 개수가 많지 않아 그동안의 비용 절감 이득이 더 크다고 판단.

서버 인스턴스 — 리소스 산정 및 결론

  • 상황: prod/dev 환경을 나눠서 각 인스턴스의 CPU/메모리 요구량을 추정해야 함.
  • 결론: dev는 t4g.small, prod는 t4g.small(앱서버) + t4g.micro(DB서버)
  • 이유: CPU는 이전에 개발한 커뮤니티 서비스가 2코어로 동작했던 경험을 근거로 삼음(정확한 근거는 아니라 개선 여지 있음). 메모리는 nginx 100 + FE 100 + BE 1024 + OS 500 + MySQL 500 = 2.2GiB로 산정해, 2CPU/2GiB 이상 모델 하나와 2CPU/1GiB 모델 하나가 필요하다고 판단(이 역시 이전 서비스 운영 경험 기반이라 개선 여지 있음).

예상 비용

  • 상황: 위에서 정한 인스턴스 구성을 기준으로 월 비용을 추정해야 함.
  • 결론: 온디맨드 기준 월 $37.59, Spot 할인 적용 시 월 $28.08로 예상 ($15.18 + $7.59 + $5.31)
  • 이유: t4g.small×2 + t4g.micro의 온디맨드 시간당 요금(서울 리전, 아마존 비용 계산기 기준)을 30일로 환산하면 $37.59. Dev는 안정성이 떨어져도 상관없는 환경이라 Spot 인스턴스로 전환해 65% 비용 절감을 적용.

아키텍처 다이어그램

image-12

(이미지가 안 보이는 환경을 위한 Mermaid 버전 — AWS 인프라 구성만, CI/CD는 제외)

flowchart TB
    Client(["사용자 Client<br/>Web / Mobile"])

    subgraph DEV["Dev EC2 — t4g.small (Spot)"]
        DevInfo["SG In: 80,443,22 · Out: 80,443<br/>Restart Policy: no"]
        DevNginx["nginx<br/>80/443 → HTTPS 리다이렉트"]
        DevInfo -.- DevNginx
        DevFE["FE :3000"]
        DevBE["BE Spring/JVM :8080<br/>non-root · DB healthcheck 대기 후 기동"]
        DevDB[("MySQL :3306<br/>로컬 디스크, Volume 미사용<br/>재배포 시 초기화")]
        DevNginx -->|"/  :3000"| DevFE
        DevNginx -->|"/api  :8080"| DevBE
        DevBE -->|":3306"| DevDB
    end

    subgraph PRODVPC["Prod VPC — ap-northeast-2"]
        subgraph PRODAPP["Prod 앱서버 EC2 — t4g.small (On-Demand)"]
            ProdAppInfo["SG In: 80,443,22 · Out: 80,443,3306(→DB)<br/>리소스 제한 · Restart: unless-stopped"]
            ProdNginx["nginx<br/>80/443 → HTTPS 리다이렉트"]
            ProdAppInfo -.- ProdNginx
            ProdFE["FE :3000"]
            ProdBE["BE Spring/JVM :8080"]
            ProdNginx -->|"/  :3000"| ProdFE
            ProdNginx -->|"/api  :8080"| ProdBE
        end
        subgraph PRODDB["Prod DB서버 EC2 — t4g.micro (Private Subnet)"]
            ProdDBInfo["SG In: 3306(출발지=앱서버 SG만),22<br/>Out: 없음"]
            ProdDB[("MySQL :3306<br/>Volume 미사용")]
            ProdDBInfo -.- ProdDB
        end
        ProdBE -->|"3306, private"| ProdDB
    end

    subgraph LOG["로깅"]
        CW["AWS CloudWatch Logs<br/>stdout/stderr 수집"]
    end

    Client -->|"dev.example.com : 80/443"| DevNginx
    Client -->|"example.com : 80/443"| ProdNginx
    DEV -.로그 전송.-> CW
    PRODAPP -.로그 전송.-> CW
    PRODDB -.로그 전송.-> CW
Loading

다이어그램 설계 의도

  • Dev: nginx + FE + BE + MySQL을 EC2(t4g.small, Spot) 한 대에 통합 — nginx, FE는 리소스 사용량이 적어 이보다 더 잘게 나눠 지원하는 EC2가 없고, dev는 다양한 테스트를 위해 컨테이너 리소스도 제한하지 않음. 안정성보다 빠른 구축과 비용 절감(Spot 65% 할인)을 우선한 구성.
  • Dev DB는 로컬 디스크, Volume 미사용 — dev 환경에서는 DB에 종속성을 요구하지 않기로 해, 재배포·재시작마다 초기화되도록 함. Spot 회수 시 데이터가 함께 사라지는 것도 감수.
  • Prod: nginx + FE + BE는 EC2(t4g.small) 한 대, DB는 별도 EC2(t4g.micro, VPC private)로 분리 — 다수 사용자가 이용하는 prod는 안정성이 중요해 DB를 물리적으로 분리하고, 컨테이너별 최대 리소스도 제한. RDS 대신 EC2에 MySQL을 직접 올린 이유는 이 규모에서 RDS 비용이 과하기 때문.
  • ALB 대신 nginx — 이 규모의 트래픽에 ALB 기본요금은 과해서, nginx가 진입점 겸 리버스 프록시 역할을 겸함.
  • nginx → FE(3000), nginx → BE(8080), (dev만) BE → DB(3306) — 컴포넌트 간 필요한 포트만 최소로 열어 통신. 외부와 맞닿는 지점은 nginx뿐이라 내부 통신(FE, BE)은 HTTP로 진행하고, 외부 진입(80/443)만 HTTPS로 리다이렉트해 스니핑을 방지.
  • SSH 22가 dev, prod 앱서버(nginx 호스팅 EC2)에 열려있음 — 이상적으로는 nginx 전용 EC2만 외부에 노출되는 게 좋지만, 하나의 EC2에 nginx·FE·BE를 함께 구성하다 보니 불가피한 구조. 대신 SSH 키는 git ignore로 관리해 위험을 상쇄.
  • Docker Network 미분리 — dev는 DB에 중요한 정보가 없어 네트워크 분리 이점이 적고, prod는 EC2 자체가 VPC private으로 이미 분리돼 있어 관련이 적음. 안정성보다 v1의 빠른 구축을 우선한 결정.

2. Docker 컨테이너 구성 설계

컨테이너 실행 권한

  • 상황: 컨테이너가 외부에 노출될 경우 터미널 접근이 남용될 수 있음.
  • 결론: non-root로 실행
  • 이유: 외부자가 터미널에 접근해 권한을 남용하는 것을 방지하기 위함.

nginx / FE / BE 배치

  • 상황: 컴포넌트별로 EC2를 분리할지, 한 인스턴스에 통합할지 결정해야 함.
  • 결론: nginx, FE, BE를 한 EC2에 통합
  • 이유: nginx와 FE는 리소스 사용량이 매우 적어서, 이보다 더 작은 단위로 지원하는 EC2 인스턴스가 없음.

로드밸런서

  • 상황: 트래픽을 받는 진입점 역할이 필요함.
  • 결론: ALB 대신 nginx 사용
  • 이유: 이 정도 규모의 인프라를 운영하기에는 ALB의 기본요금이 과함.

DB 서비스 구현 방식

  • 상황: 관리형 DB(RDS)를 쓸지, EC2에 직접 구축할지 결정해야 함.
  • 결론: RDS 대신 EC2에 MySQL 직접 구성
  • 이유: 이 정도 규모의 서비스에서 RDS는 비용이 과함.

배치(알림) 작업 위치

  • 상황: 유통기한 임박 알림을 위한 배치 작업을 별도로 분리할지 결정해야 함.
  • 결론: 별도 컴포넌트로 분리하지 않고 BE에 통합
  • 이유: MAU가 크지 않아 배치 작업이 BE 리소스에 주는 부담이 크지 않을 것으로 판단.

로깅

  • 상황: 배포 확인과 문제 추적을 위한 기본 로깅 체계가 필요함.
  • 결론: AWS CloudWatch Logs 사용
  • 이유: 별도의 로깅 전용 EC2를 두지 않고도 매니지드 서비스로 로그를 수집·조회할 수 있어, 애초에 Loki/Promtail 같은 구성을 배제했던 이유였던 "로깅 전용 EC2 운영 비용" 문제를 그대로 해결함. 이미 AWS(EC2 등) 위에서 서비스를 운영 중이라 별도의 인프라 구축 없이 자연스럽게 연동할 수 있다는 점도 함께 고려함.
  • 트레이드오프: CloudWatch는 로그 수집·저장·조회량에 따라 과금되므로, 로그양이 늘어나면 비용이 발생함 — 완전히 무료였던 stdout/stderr 단순 로깅보다 비용 부담이 생김. 또한 AWS 서비스에 대한 의존도가 한 단계 더 높아져, 추후 다른 클라우드로 이전할 경우 로깅 체계도 함께 옮겨야 하는 부담이 생김.

Dev 환경 구성

  • 상황: dev는 다양한 개발/테스트를 지원해야 하고, BE가 DB 준비 상태에 의존적임.
  • 결론: nginx, FE, BE, DB를 한 EC2에 배치, 리소스 제한 없음, BE는 healthcheck·depends_on으로 DB 준비 후 기동
  • 이유: 개발 단계에서 다양한 테스트를 자유롭게 진행할 수 있도록 리소스를 제한하지 않기로 함. BE가 DB에 종속적이라 DB가 뜨기 전에 BE가 먼저 기동되는 것을 막기 위함.

Dev DB 데이터 유지 (Volume)

  • 상황: dev 컨테이너를 재배포/재시작할 때 DB 데이터를 유지할지, 매번 초기화할지 결정해야 함.
  • 결론: Volume 미사용 — 배포할 때마다 DB가 초기화·재작성됨
  • 이유: dev 환경에서는 DB에 대한 종속성을 요구하지 않기로 함. 개발자가 다른 데이터를 넣어 테스트하고 싶을 수 있는 만큼, 오히려 배포마다 깨끗하게 초기화되는 편이 낫다고 판단.

Prod 환경 구성

  • 상황: prod는 다수 사용자가 이용하므로 안정성이 중요함. DB도 별도로 분리할지 결정해야 함.
  • 결론: nginx, FE, BE는 한 EC2, DB는 별도 private VPC EC2에 분리. 컨테이너별 최대 리소스 제한 적용.
  • 이유: 서비스 안정성을 위해 특정 컨테이너가 리소스를 과다 사용해 다른 컨테이너에 영향을 주는 것을 막기 위함.

3. Docker Compose 기반 배포 구조 설계

EC2 구성

  • 상황: 1번에서 정한 인스턴스 구성을 배포 구조에도 반영해야 함.
  • 결론: dev는 t4g.small, prod는 t4g.small + t4g.micro
  • 이유: ARM(t4g)이 성능 대비 비용이 저렴하고 저전력임. x86보다 라이브러리 호환성은 떨어지지만, MVP에서 사용하는 라이브러리 중 ARM에 호환 안 되는 것이 없어 ARM을 선택.

Docker Network

  • 상황: 가장 쉽게 뚫릴 수 있는 FE → DB 직접 접근 경로를 네트워크 단에서 막을지 결정해야 함.
  • 결론: dev, prod 모두 별도 네트워크 분리 미구현
  • 이유: dev는 DB에 중요한 정보가 없어 네트워크 분리의 이점이 적음. prod는 DB가 이미 별도 EC2(VPC private)로 분리돼 있어 관련이 없음. dev에 중요한 정보를 저장할 가능성을 고려하면 네트워크 분리가 이상적이지만, v1의 목표인 빠른 인프라 구축을 위해 안정성을 일부 희생하기로 함.

Container간 통신 포트

  • 상황: 컴포넌트 간 통신에 사용할 포트를 정해야 함.

  • 결론:

    구간 포트
    사용자 → nginx HTTP 80/443
    nginx → FE 3000
    nginx → BE 8080
    (dev만) BE → DB 3306
  • 이유: nginx로 진입할 때는 평문 전송 시 스니핑 위험이 있어 HTTPS를 지원하고 HTTP는 HTTPS로 리다이렉트. nginx 내부(FE, BE와의 통신)는 외부와 맞닿는 지점이 nginx뿐이라 HTTP로 진행해도 문제가 거의 없다고 판단.

서버 관리 접근 방식 — SSH vs SSM

  • 상황: 서버에 원격으로 접속해 관리·배포 작업을 하려면 접근 방식을 정해야 함. AWS SSM Session Manager와 SSH 중 선택 가능.
  • 결론: SSH 선택
  • 이유: SSM Session Manager는 포트를 열지 않고 IAM 기반으로 접근을 통제할 수 있어 보안적으로 더 우수하지만, SSM 에이전트·IAM 역할·VPC 엔드포인트 설정 등 초기 구축 절차가 SSH보다 번거로움. v1은 빠른 구축이 최우선 목표라, 22번 포트를 열어야 하는 보안적 헛점을 감수하고 익숙하고 바로 적용 가능한 SSH를 선택하는 트레이드오프를 함. 대신 SSH 키는 git ignore로 관리해 위험을 일부 상쇄(아래 "외부 진입 경로" 참고).

외부 진입 경로 / SSH

  • 상황: 서버 관리와 배포를 위한 외부 접근 경로가 필요함.
  • 결론: SSH 22를 dev, prod 앱서버(nginx 호스팅 EC2) 모두에 개방. SSH 키는 git ignore 처리.
  • 이유: 이상적으로는 nginx만 있는 EC2만 외부에 노출되는 것이 좋지만, v1 특성상 안정성을 조금 포기하고 빠른 구축을 위해 하나의 EC2에 nginx, FE, BE를 함께 구성하다 보니 어쩔 수 없는 구조가 됨. 대신 SSH 키 관리를 git ignore로 처리해 위험을 상쇄.

보안그룹 / 포트

  • 상황: 위 통신 구조를 보안그룹(inbound/outbound)으로 구체화해야 함.

  • 결론:

    대상 Inbound Outbound
    Dev EC2 80, 443, 22 80, 443
    Prod 앱서버 (nginx+FE+BE) 80, 443, 22 80, 443, 3306 (→Prod DB)
    Prod DB서버 22, 3306 (출발지: 앱서버) 없음
  • 이유: 각 EC2가 실제로 주고받는 트래픽(사용자 접속, 앱서버-DB 통신, SSH 관리 접근)만 최소로 열기 위함.

Volume

  • 상황: dev/prod DB의 영속성 필요 여부를 정리.
  • 결론: dev, prod 모두 Volume 미사용
  • 이유: dev는 DB 종속성을 유지할 필요가 없다고 판단 (위 "Dev DB 데이터 유지" 항목과 결론 동일). prod는 DB가 별도 EC2에 있어 이 항목과 무관.

Environment Variable

  • 상황: 민감하지 않은 설정값을 어떻게 관리할지 정해야 함.
  • 결론: EC2 내 .env 파일로 관리 (DB url, DB username 등)
  • 이유: 일반 사용자에게 감추면 좋지만 드러나도 큰 상관이 없는 값들이라 .env로 충분하다고 판단.

Secret 관리

  • 상황: DB 비밀번호, JWT 시크릿처럼 노출 시 보안 위협이 큰 값을 안전하게 관리할 방법이 필요함.
  • 결론: GitHub Secrets 사용
  • 이유: Docker Compose Secret은 한 EC2 안에서 Secret과 서비스를 함께 다뤄, 하나만 털려도 전체가 노출되는 구조라 보안상 좋지 않다고 판단. AWS Secrets는 다른 클라우드로 이전하면 무용지물이 됨. GitHub Secrets는 CI를 계속 GitHub에서 사용할 예정이라 이런 단점이 발생하지 않음.

Container Restart 정책

  • 상황: 컨테이너가 종료됐을 때 자동 재시작할지 여부를 dev/prod 각각 정해야 함.
  • 결론: dev는 no(재시작 안 함), prod는 unless-stopped
  • 이유: dev는 개발 단계에서 컨테이너가 꺼진 원인을 먼저 확인하고 해결하는 것이 중요한데, 자동으로 다시 시작하면 문제를 해결하기 전에 같은 에러가 반복될 수 있음. prod는 서비스 중단을 막기 위해 비정상 종료 시 자동으로 다시 켜지도록 하되, 정상 종료 후 재부팅 시에는 자동 시작하지 않음 — Auto-Scaling을 도입한다면 필요하지만 아직은 아니라고 판단.

Image Registry

  • 상황: Docker 이미지를 어디에 보관하고 배포할지 결정해야 함.
  • 결론: GHCR
  • 이유: 프로젝트가 GitHub를 주로 사용할 것이 확정적이라, GitHub Actions를 잘 활용할 수 있는 GHCR을 선택.

Image Tag 정책

  • 상황: 배포된 이미지의 버전을 어떻게 식별하고 롤백할지 결정해야 함.
  • 결론: Git SHA
  • 이유: latest처럼 간단하고 빠르면서도, SHA이기 때문에 커밋 단위로 마지막 버전을 구분할 수 있어 v1에 적합하다고 판단.

4. 현재 구조의 한계 정의

  1. 가용성 (단일 장애점) — dev/prod 앱서버 모두 한 EC2에 컴포넌트가 몰려 있고 nginx가 유일한 진입점이라 SPOF. Prod DB도 이중화·failover 없음
  2. 확장성 — EC2 대수 고정, Docker Compose엔 오케스트레이션 기능이 없어 오토스케일링 불가
  3. 리소스 여유 부족 — 메모리 예산 2.2GiB vs t4g.small 2GiB, Prod DB(t4g.micro 1GiB)도 여유 빠듯
  4. Dev 데이터 안정성 — Dev DB는 Volume을 사용하지 않아 Spot 회수뿐 아니라 재배포·재시작만으로도 매번 초기화됨. 개발 편의상 감수한 트레이드오프이나, 데이터가 필요한 테스트 시나리오에서는 매번 재시딩이 필요함
  5. 보안 노출면 — SSH 22 무제한 오픈, Docker Network 미분리로 FE→DB 경로 존재
  6. Secret 관리 한계 — GitHub Secrets는 CI 단계만 커버, 배포된 EC2의 .env 런타임 로테이션은 자동화 안 됨
  7. 운영/관측성 부재 — stdout/stderr 로깅뿐, 중앙 수집·알림 체계 없음
  8. 구조적 결합도 — 알림 배치가 BE 프로세스에 통합되어 있어 리소스 경합 가능

대부분 "v1을 빠르게 구축한다"는 목표를 위해 의도적으로 감수한 트레이드오프

⚠️ **GitHub.com Fallback** ⚠️