--- title: blog-topic / env-example-drift-gate-gradle-2026-06-06 source_type: blog-topic status: raw related_branches: [feature-env-driven-runtime-configuration] related_projects: [ca-tmpl] tags: [blog-topic, ca-tmpl, gradle, configuration, 12-factor, fail-fast, developer-experience] created: 2026-06-06 status_label: ready-for-canonical target_audience: backend-engineer inspiration_url: archive_url: --- # blog-topic: env-example-drift-gate-gradle-2026-06-06 > Layer: `raw/blog-topics/` — 작업에서 나온 블로그 글감 원석. canonical 정제 전 raw 후보. ## Parent / 부모 - [[raw/branch-notes/feature-env-driven-runtime-configuration]] ## 글감 한 줄 "`.env.example` 을 추가하려다 깨달은 것 — 우리 `.env` 는 이미 tracked 였다. drift 게이트의 정답 소스는 템플릿이 아니라 코드가 실제로 요구하는 surface 다." ## 핵심 논지 - 흔한 패턴: secret 때문에 `.env` 를 gitignore 하고 redacted `.env.example` 을 commit. 하지만 `.env.example` 은 "작성 시점 스냅샷"이라 새 env 가 생겨도 갱신 안 돼 drift → 신규 합류자가 복사해 띄우면 누락 env startup 실패. - **반전(이 프로젝트의 실제 결정)**: ca-tmpl 은 `src/.env` 자체를 git-tracked 로 둔다(로컬 dev 기본값 포함, secret 은 로컬 sentinel). 이 경우 `.env.example` 은 **password 만 가린 중복 사본**이라 가치가 거의 없다. 처음엔 D7 문언대로 `.env.example` + `verifyEnvExample` 을 만들었다가, 리뷰에서 "`.env` 가 이미 tracked 인데 example 이 왜 필요?"라는 지적으로 제거 → task 를 `verifyEnvKeys` 로 rename. - 결론적 게이트(custom Gradle task `verifyEnvKeys`)는 **template 파일이 아니라 코드 surface 를 기준**으로 두 방향만 강제: 1. `application.yml` 의 `${VAR}`(inline default 없는 것=required) 가 전부 `src/.env` 에 존재(누락 0). 2. `src/.env` 의 모든 키가 `application.yml` 어딘가 `${...}` 로 실제 소비됨(orphan/stale 0). - `inputs.files(...)` 선언으로 Gradle up-to-date 캐싱과 호환, `check` 에 `dependsOn` 연결해 CI 필수 게이트화. - 교훈: "`.env.example` drift 막기"는 수단이지 목적이 아니다. 진짜 목적은 "코드가 요구하는 env 와 운영자가 가진 env 가 일치하는가". `.env` 가 tracked 라면 example 은 군더더기이고, 게이트는 application.yml ↔ `.env` 를 직접 보는 게 맞다. ## 왜 registry 기준이 아니라 application.yml surface 기준인가 (설계 결정) - contract registry(`env-keys.yaml`)는 **여러 미구현 branch 의 키까지** 포함 → registry 와 `.env` 를 1:1 강제하면 코드에 없는 phantom 키 수십 개를 넣어야 함(운영자가 무시할 값). - "운영자가 `.env` 만으로 앱을 띄울 수 있는가"가 진짜 목적 → 검증 기준은 **실제 config surface(application.yml placeholder)** 가 맞다. registry 는 governance SSOT 로 별도 유지. - 교훈: drift gate 의 "정답 소스"는 빌드 가능한 표면이어야지, 미래 계약을 담은 레지스트리가 아니다. ## 곁가지 주제 - 12-factor §III config 와 `APP_` prefix 전면 통일(외부 의존 env 와 시각 분리)의 트레이드오프. - boolean `true/false`-only, Duration `30s`-only 같은 "기계적으로는 동등하나 팀 규약으로 1택" 결정을 어떻게 문서화/강제하나. ## 트리거 / Trigger - 트리거 유형: `branch-work` - 트리거 날짜: 2026-06-06 - 트리거 연결 노트: [[raw/branch-notes/feature-env-driven-runtime-configuration]] ## 글감 / Topic seed - 한 문장 요지: `.env.example` drift를 막는 목적은 example 파일 유지가 아니라 실제 config surface와 실행 env key의 정합성을 검증하는 것이다. - 예상 제목 후보: - `.env.example`이 아니라 실제 config surface를 검증하기 - Gradle task로 env key drift를 막는 방법 ## 핵심 주장 후보 / Claim candidates - 사실 후보: - ca-tmpl은 env-driven runtime configuration branch에서 env key drift gate를 다뤘다. - tracked `.env`와 `application.yml` placeholder의 양방향 정합을 보는 방향이 글감의 핵심이다. - 의견/해석 후보: - drift gate의 정답 소스는 미래 registry가 아니라 현재 빌드 가능한 runtime surface여야 한다. ## Outline seed 1. `.env.example`은 snapshot이라 drift가 생기기 쉽다. 2. tracked `.env` 정책에서는 example 사본보다 key surface 검증이 더 중요하다. 3. Gradle `verifyEnvKeys`가 application config와 env key를 양방향으로 확인한다. ## Canonical 전환 후보 / Canonical extraction candidates - `wiki/projects/ca-tmpl/config-and-adapter-templates.md` 후보: - env key drift gate와 `.env` tracked policy. - 필요한 추가 검증: - 실제 `verifyEnvKeys` 구현 여부와 현재 ca-tmpl의 `.env` 추적 정책. ## Sources / 근거 후보 - [[raw/branch-notes/feature-env-driven-runtime-configuration]] - [[raw/interviews/startup-fail-fast-config-validation-2026-06-06]] ## 미해결 / Unknown - 아직 확인해야 할 사실: `verifyEnvKeys`가 현재 ca-tmpl 코드에 존재하는지. - 과장하면 안 되는 부분: draft canonical 확인 전까지 구현 완료처럼 쓰지 않는다. ## 관련 / Related - [[raw/branch-notes/feature-env-driven-runtime-configuration]] - [[raw/interviews/startup-fail-fast-config-validation-2026-06-06]] ## Decision / 처리 결정 - 액션: `promote-to-canonical` - 이유: `wiki/projects/ca-tmpl/config-and-adapter-templates.md` 에 env key drift gate 글감으로 반영했다. - 다음 단계: target canonical이 아직 `draft` 이므로 `blogify` 전 branch-note/code evidence와 현재 `.env` tracked 정책을 재확인한다.