환경변수 중앙화는 저장보다 경계 설계다: Infisical Self-Hosted 도입기
다른 호텔의 환경변수가 운영 배포에 섞였다
Infisical을 도입하기 전에는 데이터베이스 접속 정보, 외부 API 인증값과 배포용 시크릿을 Jenkins UI·로컬 .env·Slack을 통해 나누어 관리했습니다.
가장 큰 문제는 보관 장소가 여러 곳이라는 사실 자체가 아니었습니다. 여러 호텔을 배포하는 과정에서 다른 호텔의 환경변수가 포함된 상태로 운영 배포가 완료되는 문제가 발생했습니다.
배포 직후 운영 사이트를 직접 점검하면서 예약·조회 요청이 실패하고, 화면과 기능 설정도 다른 호텔 기준으로 표시되는 것을 확인했습니다. 환경변수를 바로잡고 다시 배포해 정상화하기까지 약 10분이 걸렸으며, 이 시간은 운영 서비스 장애로 판단했습니다.
다만 환경변수가 섞인 정확한 원인은 끝까지 규명하지 못했습니다. 여러 배포가 같은 Jenkins 대기열에 있던 상황에서 발생했지만 Jenkins 자체 오류라고 단정할 근거는 없었고, 사람이 다른 프로젝트의 값을 잘못 등록했을 가능성도 배제할 수 없었습니다.
이 장애를 계기로 다음 세 문제를 순서대로 해결해야 한다고 판단했습니다.
- 프로젝트 간 환경변수가 섞이지 않아야 한다.
- 어떤 값이 최신이고 누가 어떤 목적으로 관리하는 값인지 확인할 수 있어야 한다.
- 새 프로젝트나 팀원이 생길 때 시크릿 원문을 다시 전달하지 않아야 한다.
한곳에 모으는 것보다 책임을 나눴다
하네스의 시크릿 구조와 CI/CD 조회 흐름을 직접 설계하고 구현하면서 Infisical을 환경변수의 단일 원천으로 도입했습니다.
Infisical Cloud와 Self-Hosted를 정식 비교한 뒤 선택한 것은 아니었습니다. 비용 부담도 고려했지만, 사내 시크릿을 외부 Cloud에 보관하는 것에 대한 개인적인 우려가 더 컸기 때문에 사내 서버에 직접 구축하는 방향으로 진행했습니다.
환경변수를 하나의 폴더에 모두 넣지는 않았습니다. 최초 도입 단계부터 프로젝트, 환경과 실행 목적에 따라 경계를 나눴습니다.
| 경로 | 책임 |
|---|---|
/frontend | 프론트엔드 런타임 환경변수 |
/backend | 백엔드 런타임 환경변수 |
/frontend/github-actions | 프론트엔드 배포 전용 값 |
/backend/github-actions | 백엔드 배포 전용 값 |
Shared-Secrets/slack | 여러 프로젝트가 공통으로 사용하는 Slack 값 |
Shared-Secrets/vercel | 여러 프로젝트가 공통으로 사용하는 Vercel 값 |
각 프로젝트 안에서는 dev와 prod 환경을 분리했습니다. GitHub Secrets에는 Infisical에 접근하기 위한 Client ID와 Client Secret만 남기고, 실제 런타임·배포 변수의 원본은 Infisical에서 관리했습니다.
공용 Slack·Vercel 키는 Shared-Secrets 한곳에서 갱신하면 이를 사용하는 프로젝트들이 같은 원본을 조회합니다. 신규 프로젝트는 정해진 경로와 Machine Identity를 연결해 시크릿 원문을 다시 전달하지 않고 구성할 수 있습니다.
로컬 개발과 배포의 조회 시점을 다르게 두었다
로컬 개발에서는 하네스 래퍼를 통해 Infisical CLI로 값을 조회하는 방식을 기본으로 두고, Infisical에 연결할 수 없는 경우에만 로컬 .env로 대체할 수 있게 했습니다.
배포 구조를 정할 때는 애플리케이션 서버가 실행 중에 Infisical과 직접 통신하는 방식도 검토했습니다. 하지만 Infisical 서버가 중단되면 새 인스턴스의 시작이나 재시작에 필요한 환경변수를 가져오지 못할 수 있다고 판단했습니다.
그래서 GitHub Actions가 배포 단계에서 환경별 값을 조회해 .env 파일을 만들고, 이를 애플리케이션 서버에 배치하도록 설계했습니다.
로컬 개발
개발자 → 하네스 래퍼 → Infisical
└─ 연결 불가 시 로컬 .env
배포
GitHub Actions → Infisical에서 환경별 값 조회
→ .env 파일 생성
→ 애플리케이션 서버에 배치
운영
실행 중인 서비스 → 마지막 배포의 .env 사용
→ Infisical 런타임 통신 없음
이 구조에서는 Infisical 조회가 실패하면 새 배포가 중단됩니다. 이미 실행 중인 서비스는 마지막 배포의 .env를 사용하므로 Infisical의 현재 상태에 직접 의존하지 않습니다.
중단 테스트로 배포 실패 경계를 확인했다
이 경계가 의도대로 동작하는지 확인하기 위해 별도의 테스트 프로젝트와 서버에서 Infisical을 의도적으로 중단한 뒤 배포를 실행했습니다.
GitHub Actions는 시크릿 조회 단계에서 실패해 새 배포가 진행되지 않았습니다. 반면 기존에 실행 중이던 테스트 서비스는 정상적으로 동작했고, 이전 배포의 .env를 계속 사용했습니다.
이 테스트로 확인한 범위는 배포 시점의 조회 실패와 기존 프로세스의 동작 여부까지입니다. 운영 장애 복구, 서비스 재시작이나 스케일 아웃 상황까지 검증한 결과로 확대하지 않습니다.
분리한 경계와 아직 분리하지 못한 경계
도입 전
Jenkins UI ─┐
로컬 .env ─┼─ 프로젝트 간 값 혼입 → 약 10분 운영 장애
Slack ──────┘
도입 후
Infisical SSOT
├─ 프로젝트
│ ├─ dev / prod
│ ├─ frontend / backend
│ └─ runtime / github-actions
└─ Shared-Secrets
├─ slack
└─ vercel
배포 실패 경계
Infisical 중단 → 새 배포 중단
└─ 기존 서비스는 마지막 .env로 동작
남은 복구 경계
Infisical DB + 같은 인스턴스의 백업 파일
└─ 함께 손실되면 현재 복구 불가
연결된 작업물의 CI/CD 스윔레인은 변경 감지부터 배포 확인까지의 실행 순서를 다룹니다. 이 인사이트의 architecture는 같은 흐름을 반복하지 않고, 환경변수의 소유권과 배포·복구 실패 경계를 비교합니다.
동일 유형의 혼입은 다시 발견하지 못했다
Infisical로 전환한 뒤 2026년 8월 현재까지 유지보수하는 동안 환경변수가 다른 호텔의 운영 배포에 섞이는 동일 유형 문제는 다시 발견하지 못했습니다.
이는 시스템 전수 집계나 장애율 통계가 아닙니다. 운영 과정에서 관찰한 범위이며, 배포 시간 단축이나 온보딩 시간 개선처럼 측정하지 않은 효과도 주장하지 않습니다.
실제로 확인한 변화는 공용 Slack·Vercel 키를 한곳에서 갱신하고, 신규 프로젝트가 정해진 경로와 Machine Identity를 통해 같은 원본을 사용하게 된 점입니다.
중앙화했다고 복구 가능성이 생기는 것은 아니다
현재 Self-Hosted Infisical은 데이터베이스 백업 파일을 만들지만 그 파일도 같은 인스턴스 서버에 있습니다. 자동 장애 조치나 다중 인스턴스 구성도 없습니다. 따라서 데이터베이스와 백업 파일이 함께 삭제되면 현재 구조로는 복구할 방법이 없습니다.
중앙화로 환경변수의 소유권과 배포 실패 경계는 분리했지만, 백업의 장애 경계까지 분리한 것은 아니었습니다. 외부 저장소로 백업을 분리하고 실제 복구 훈련까지 확인하는 개선은 앞으로의 과제입니다. 아직 구현하지 않은 고도화나 DX 개선은 완료된 기능처럼 공개하지 않습니다.
운영 부담을 감수하더라도 사내 시크릿의 Self-Hosted 선택은 유지하는 편이 낫다고 판단합니다. 다만 Self-Hosted를 선택하는 것과 동일 인스턴스 백업 구조를 유지하는 것은 다른 결정입니다. 중앙화, 배포 격리와 복구 가능성은 각각 별도의 경계로 설계하고 검증해야 합니다.