ADR 0004: STG runtime 설정과 Canonical DB 전환 경계¶
상태: Accepted 작성일: 2026-09-03 범위: Vercel Next BFF, Azure Gateway·VM runtime, PostgreSQL baseline, STG 배포 gate
Context¶
STG 배포 전에 애플리케이션 기능뿐 아니라 실행 환경의 연결 계약을 고정해야 한다. 현재 저장소에서 확인된 위험은 다음과 같다.
- Next BFF의 Backend origin이 세 파일에 중복되어 있고, 배포 환경에서
PROCEDURE_API_BASE가 누락돼도localhost:8005로 조용히 fallback한다. - Azure CD가
STG_ENV또는PROD_ENV를 Docker build context에 만들고, Dockerfile이 그.env를 image layer에 복사한다. - Backend runtime은 단일 Alembic baseline과 PostgreSQL data snapshot만 사용하지만, 현재 snapshot은 STG 직전에 다시 만들어야 하는 중간본이다.
- 기존 STG DB의 Alembic history는 새 단일 baseline과 호환되지 않을 수 있다. 짧은 점검 중단은 허용되므로 dual write, legacy fallback DB, 병렬 runtime은 만들 필요가 없다.
- Gateway의 실제 route·write 차단 설정, VM의
deploy.sh, Vercel의 현재 환경변수 값은 이 저장소만으로 확인할 수 없다.
이 결정은 API URL·request·response 구조와 데이터 의미를 바꾸지 않는다. 배포 환경별 연결과 비밀값 전달 방식을 정리하는 작업이다.
Decision¶
1. 실행 topology¶
Browser
└─ same-origin /api/*
└─ Vercel Next Route Handler
└─ PROCEDURE_API_BASE (server-only origin)
└─ Azure Gateway /procedure-hub/*
└─ Azure VM procedure-hub container
└─ DATABASE_URL
└─ Azure PostgreSQL canonical DB
- Browser는 지금처럼 같은 origin의 Next
/api/*만 호출한다. - Backend origin은 browser bundle에 넣지 않는다.
NEXT_PUBLIC_환경변수로 노출하지 않는다. - Gateway path
/procedure-hub와 API path/api/v1은 환경값이 아니라 공개 API 계약이므로 코드의 단일 상수로 유지한다.
2. Next BFF 환경 설정¶
PROCEDURE_API_BASE는 scheme과 host만 포함한 origin으로 정의한다.
허용: https://api-stg-csp.medisolveai.com
정규화 후 허용: https://api-stg-csp.medisolveai.com/
금지: https://api-stg-csp.medisolveai.com/procedure-hub
마지막 /는 정규화할 수 있지만 path, query, fragment가 포함된 값은 설정 오류로 처리한다.
Next server 설정 모듈 한 곳만 process.env.PROCEDURE_API_BASE를 읽고 모든 BFF route가 이를
사용한다.
- 모든 실행 환경에서 origin을 명시한다. 로컬 주소는 Compose·env에서 지정한다.
- 미지정·잘못된 URL이면 localhost로 fallback하지 않고 명시적인 설정 오류로 실패한다.
PROCEDURE_API_BASE는 server runtime 값으로 읽는다. NEXT_PUBLIC_ 값은 build 시 browser
bundle에 고정되므로 Backend routing에 사용하지 않는다. 과거 버전 배지 전용
NEXT_PUBLIC_APP_ENV와 NEXT_PUBLIC_APP_VERSION은 미사용 배지 제거와 함께 코드에서 제거했다.
3. 환경별 설정 계약¶
| 소유 위치 | Local | STG | PROD |
|---|---|---|---|
Next PROCEDURE_API_BASE |
http://127.0.0.1:8005 |
https://api-stg-csp.medisolveai.com |
https://api-csp.medisolveai.com |
Backend API_PREFIX |
/procedure-hub |
/procedure-hub |
/procedure-hub |
Backend DATABASE_URL |
공용 로컬 Postgres의 procedure_hub DB |
STG PostgreSQL TLS DSN | PROD PostgreSQL TLS DSN |
Backend DATABASE_ECHO |
필요 시 명시 | false |
false |
Backend CORS_ORIGINS |
Local FE origin | 승인된 STG browser origin | 승인된 PROD browser origin |
CORS_ORIGINS는 인증 수단이 아니다. Server-to-server 소비자는 CORS의 적용 대상이 아니며,
write 인증·인가는 계속 Gateway가 소유한다.
4. Backend 비밀값은 runtime에만 주입¶
Docker image는 환경과 무관한 동일 runtime 산출물로 만든다.
- CD build job은
backend/envs/.env를 만들거나 환경 secret을 읽지 않는다. - Dockerfile은
.env를 복사하지 않는다. .dockerignore는 실제.env파일이 build context에 들어가는 것을 차단한다.- 인프라 담당자가 VM의
/opt/procedure-hub/config/{environment}.env를 직접 생성·관리한다. CD는 이 파일을 전달하거나 덮어쓰지 않으며, deploy script는 container 시작 시--env-file로 연결한다. - 파일은 deploy 계정 소유, mode
600으로 둔다. 로그와 image metadata에 값이 나타나지 않게 한다.
첫 전환에서는 Key Vault, sidecar, 별도 secret sync agent를 추가하지 않는다. VM runtime
env-file로 현재 문제를 해소하고, 다수 VM·자동 rotation이 실제 요구가 될 때 별도 인프라 작업으로
승격한다. Procedure Hub CD는 STG_ENV·PROD_ENV 같은 애플리케이션 env bundle secret에
의존하지 않는다.
5. DB는 점검 중단을 허용한 단일 전환¶
STG에서는 legacy DB와 canonical DB를 병행 운영하지 않는다.
- 현재 STG DB와 현재 runtime image digest를 rollback 단위로 기록한다.
- 현재 STG DB를 custom-format으로 한 번 백업한다.
- STG DB를 빈 상태로 재구성하고
b202609030001baseline을 적용한다. - 최종
procedure_hub_data.dump를--data-only로 한 번 복원한다. - Alembic revision, manifest, 핵심 row count를 확인한다.
- 새 runtime을 시작하고 기존 API·화면과 Knowledge Retrieval을 smoke한다.
Dockerfile CMD의 alembic upgrade head는 이후 동일 baseline 내 배포를 위한 기동 절차로
유지한다. seed/importer/data-init image와 JSON fallback은 다시 만들지 않는다.
Rollback은 ${environment}-rollback runtime image와 전환 직전 DB backup을 항상 함께
복원한다. 새 DB에 과거 runtime만 연결하거나 새 runtime에 과거 DB만 연결하지 않는다.
6. Publication gate는 배포와 분리¶
DB와 API를 배포한다고 clinical release를 자동 publish하지 않는다.
- 게시 release가 없을 때
/knowledge/retrieve의status=unavailable은 정상적인 fail-closed 결과다. - 관련 item을 반환하는 STG 기능 검증은 피부과 의사가 승인한 release를 명시적으로 발행한 뒤 수행한다.
- 기술 배포 성공과 임상 content 승인 상태를 하나의 자동화 단계로 합치지 않는다.
7. CI와 배포 gate¶
Backend test 정의를 복제하지 않는다. 기존 Backend Test workflow를 CD에서도 재사용하고,
Docker build는 그 성공 뒤에만 실행한다. stg·prod 직접 push로 검증을 우회하지 못하도록
protected branch의 required check로 같은 workflow를 지정한다.
Frontend는 Vercel build가 통과한 commit만 STG 대상으로 삼고, 배포 뒤 same-origin BFF smoke로
실제 PROCEDURE_API_BASE 주입을 확인한다. Vercel 설정 화면의 값 존재만으로 완료 판정하지 않는다.
8. Gateway와 write 경계¶
/procedure-hub/health,/procedure-hub/api-docs,/procedure-hub/openapi.json,/procedure-hub/api/v1/*의 route 결과를 STG에서 확인한다.- Knowledge Retrieval의 HTTP
POST는 read이고, glossaryapply·rollback과 terminologyPUT·DELETE만 semantic write라는 기존 분류를 유지한다. - write는 Gateway의 미인증 거부와 인증 허용을 각각 한 번 확인한다.
X-Uploader는 감사값이며 인증 수단이 아니다. 신뢰할 수 있는 사용자 identity 전달 경로가 확정되기 전에는 browser가 보낸 값을 그대로 신뢰하지 않고null기록을 허용한다.
Deployment-critical hardcode 판정¶
| 현재 위치 | 판정 | 처리 |
|---|---|---|
세 Next server route의 PROCEDURE_API_BASE + localhost fallback |
배포 위험 | server 설정 모듈 하나로 통합, non-development fallback 제거 |
/procedure-hub/api/v1 |
공개 계약 | 단일 상수로 유지 |
Backend CLI의 http://127.0.0.1:8005 기본값 |
로컬 실행 기본 | 유지 |
| 문서의 STG·PROD URL | 운영 문서 | 유지하되 실제 smoke 결과와 대조 |
Dockerfile의 .env COPY |
보안 결함 | 제거하고 VM runtime env-file로 전환 |
Implementation tasks¶
Task 6-1. Next Backend origin 단일화¶
목표: Next BFF가 환경별 Backend origin을 server 설정 경계 하나에서만 읽도록 변경한다.
예상 결과:
process.env.PROCEDURE_API_BASE직접 참조가 server 설정 모듈 한 곳에만 존재함- STG·PROD에서 누락된 설정이 localhost 요청으로 바뀌지 않음
- 기존 BFF path·status·response body 계약이 유지됨
최소 gate:
- 설정의 local 허용값, production 누락, path 포함 URL 세 경우만 집중 검증한다.
- 대표 일반 GET, glossary binary relay, knowledge POST를 실제 local Backend와 한 번 smoke한다.
Task 6-2. Runtime secret 전환¶
목표: Backend 설정을 image build가 아니라 VM의 container start 시점에 주입한다.
예상 결과:
- runtime image filesystem과 history에
.env가 없음 - CD build가
STG_ENV·PROD_ENV내용을 소비하지 않음 - VM deploy script가 환경별 runtime env-file을 사용함
최소 gate:
- image 한 개를 검사해
/app/.env부재와 환경변수 없이 DB 접속이 실패하는 것을 확인한다. - STG container의 마스킹된
/health/config와 실제 DB 연결을 확인한다.
Task 6-3. STG DB baseline 전환 준비¶
목표: 최종 data snapshot과 단일 baseline을 STG의 빈 PostgreSQL에 적용할 수 있는 상태로 고정한다.
예상 결과:
- STG 역할이
pg_trgmextension을 사용할 수 있음 - 최종 dump의 manifest·핵심 row count가 local 승인본과 일치함
- runtime image와 DB backup의 결합 rollback 정보가 기록됨
최소 gate:
- 빈 STG DB에 baseline과 snapshot을 한 번 적용한다.
- 전체 table 전수 비교를 새로 만들지 않고 기존 manifest와 대표 API 결과를 재사용한다.
Task 6-4. Gateway·Vercel preflight¶
목표: 저장소 밖 연결 설정이 위 topology와 권한 경계를 만족하는지 확정한다.
예상 결과:
- Vercel STG의 Backend origin이 origin-only 값으로 설정됨
- Gateway의 health·docs·read·write route 판정이 기록됨
- 미인증 write 거부와 인증 write 허용이 각각 확인됨
최소 gate:
- route 종류별 대표 요청만 사용한다. 모든 endpoint의 auth test를 복제하지 않는다.
Task 6-5. STG cutover와 사용자 검토¶
목표: 점검 중단을 열고 canonical DB와 새 runtime을 STG에 한 번 전환한 뒤 사용자 검토 상태로 둔다.
예상 결과:
- 기존 공개 API contract diff가 0임
- 핵심 화면과 BFF smoke failure가 0임
- publication 상태에 맞는 Knowledge Retrieval 결과가 확인됨
- rollback 명령, image digest, DB backup 위치가 기록됨
최소 gate:
- 기존 Backend contract suite를 배포 전에 한 번 실행한다.
- 배포 뒤 브라우저 핵심 흐름과 외부 소비 API만 smoke한다.
- 실제 rollback은 STG에서 한 번 수행해 복구 결과를 남긴다.
Change impact¶
저장소 변경 예상 범위:
src/lib/server/: Backend origin 설정 모듈src/lib/server/procedureApiProxy.tssrc/app/api/v1/glossary/[...path]/route.tssrc/app/api/v1/knowledge/retrieve/route.tsbackend/Dockerfile,.dockerignore.github/workflows/backend-test.yml,.github/workflows/csp-azure-deploy.yml- 배포·Task 문서
저장소 밖 변경 예상 범위:
- Vercel STG·PROD environment variables
- Azure VM의 runtime env-file과
deploy.sh - Azure PostgreSQL baseline 적용과
pg_trgm허용 여부 - Gateway route·write 인증 설정
- GitHub
stg·prodbranch protection
변경하지 않는 범위:
- 공개 API request·response schema와 의미
- Browser의 same-origin
/api/*호출 방식 - Canonical entity·retrieval ranking·0.90 threshold
- 임상 content 승인 상태
- Key Vault·Kubernetes·무중단 dual runtime 도입
확인된 STG preflight와 남은 결정¶
2026-09-04 read-only preflight 결과는 다음과 같다.
- 중앙
deploy.sh는 Bastion에서 Procedure Hub VM으로 접속해/opt/procedure-hub/config/stg.env를--env-file로 연결한다. INFRA PR #22·#23으로 env 연결과 필수DATABASE_URL사전 검증을 반영했고 Bastion sync를 확인했다. - Vercel STG는
PROCEDURE_API_BASE=https://api-stg-csp.medisolveai.comorigin을 사용한다. - Gateway의
/procedure-hub/*route는 현재needsAuth: false다. Backend도 write 인증을 직접 수행하지 않으므로 terminologyPUT·DELETE, glossary apply·rollback이 공개 route를 통과할 수 있다. 2026-09-04 수동 전환에서는 STG에 한해 이를 허용했고 PROD 승격 전 별도 write 보호가 필요하다. - Azure PostgreSQL server는 여러 STG DB가 공유한다.
azure.extensionsallowlist에 기존VECTOR를 유지하며PG_TRGM을 추가하고, 실제CREATE EXTENSION은procedure_hub_stg에서만 실행했다. 확인 결과 Procedure Hub는pg_trgm=1,charty_stg·linky_stg·say_stg·watch_stg는 모두pg_trgm=0이다.
확인되지 않았다는 이유로 legacy fallback을 추가하지 않는다. 특히 write 인증 문제를 runtime·DB 전환 코드에 섞지 않고 Gateway 또는 별도 인증 task로 분리한다.
Rejected alternatives¶
| 대안 | 기각 이유 |
|---|---|
Backend URL을 NEXT_PUBLIC_로 browser에 주입 |
build-time에 고정되고 Gateway origin을 불필요하게 노출함 |
| 배포 환경에서도 localhost fallback 유지 | 환경변수 누락이 명확한 설정 실패가 아니라 원인 불명의 502로 보임 |
.env를 Docker image에 계속 포함 |
image 접근 권한이 곧 환경 secret 접근 권한이 됨 |
| 기존 DB와 새 DB를 dual write·shadow 운영 | 짧은 중단을 허용하는 현재 요구보다 코드·검증·운영 비용이 큼 |
| startup seed 또는 data-init image 복원 | DB를 source of truth로 만든 현재 경계를 다시 흐림 |
| 최초 전환부터 Key Vault 도입 | 단일 VM 배포의 현재 문제를 푸는 데 필요한 범위를 넘음 |