예전 프로젝트에서는 소스코드만 배포하고 환경설정은 서버에서 직접 관리했다. 새 프로젝트에서는 환경설정도 git에 올리고 한번에 배포한다. 이게 왜 다른 건지 비교해봤다.
📌 결론 — 배포 철학 차이다
"Java는 외부 config 분리 방식이고 Python은 코드에 같이 넣는 방식인가?" — 아니다.
Python도 외부 config 분리가 가능하고, Java도 config를 코드와 함께 배포할 수 있다. 핵심 질문은 이거다.
서버가 설정을 관리하는가, Git/배포본이 설정을 관리하는가
📌 두 가지 방식 비교
방식 1 — 외부 Config 분리
소스코드만 배포하고, 환경설정 파일은 서버의 별도 경로에 둔다. 앱 실행 시점에 그 파일을 읽는다.
# Java
java -jar app.jar --spring.config.location=/opt/config/application.yml
# Python
python app.py --config=/opt/config/config.yaml
서버가 상태를 가진다. 배포해도 서버의 config는 그대로 유지된다.
방식 2 — 코드와 Config 함께 배포
config 값도 repository에 포함해서 CI/CD가 코드 + config를 함께 배포한다. 배포 시 기존 파일이 덮어쓰기된다.
Git/배포본이 상태를 가진다. 서버에서 직접 수정한 값은 다음 배포 때 사라진다.
구분 상태의 주인 배포 후 서버 수정
| 외부 Config 분리 | 서버 | 유지됨 |
| 코드와 함께 배포 | Git / 배포본 | 다음 배포 때 덮어쓰기 |
📌 내가 겪은 두 프로젝트의 차이
예전 Java 프로젝트 (방식 1)
[Bamboo] jar 빌드 → 서버에 jar만 배포
[운영 서버] /opt/config/application.yml 별도 관리
→ 배포해도 config는 건드리지 않음
→ 환경설정 변경을 굳이 커밋할 필요 없었음
새 Python 프로젝트 (방식 2)
[Bamboo] config 포함 패키징 → 전체 배포 (덮어쓰기)
[운영 서버] 프로젝트 전체가 교체됨
→ 서버에서 직접 수정해도 다음 배포 때 사라짐
→ config 변경도 git으로 관리해야 함
새 프로젝트가 방식 2를 선택한 이유 — 방화벽이 많아 서버 직접 접근이 까다로운 환경이었다. Git과 CI/CD로만 관리하는 게 더 현실적이었다.
📌 프론트는 왜 다른가 — config를 읽는 시점이 다르다
백엔드는 서버에서 실행되는 프로세스라 실행 시점에 파일이나 환경변수를 읽을 수 있다. 프론트 CSR은 구조 자체가 다르다.
npm run build → 정적 파일 생성 → nginx 서빙 → 브라우저 실행
브라우저는 이미 완성된 정적 JS 파일을 실행할 뿐이다. 빌드 시 import.meta.env.VITE_API_URL 같은 코드는 실제 값으로 치환되어 번들에 박힌다.
// 코드
const apiUrl = import.meta.env.VITE_API_URL
// 빌드 결과물 — 값이 이미 확정됨
const apiUrl = "https://api.example.com"
배포 후 서버에서 .env만 바꿔서는 효과가 없다. 재빌드가 필요하다.
구분 config 반영 시점 배포 후 수정
| 백엔드 | 런타임 (실행 시) | 가능 |
| 프론트 CSR | 빌드타임 | 재빌드 필요 |
프론트에서 배포 후 값을 바꿔야 한다면 — 런타임 config
재빌드 없이 값을 바꿔야 하는 경우, 앱 시작 시 외부 파일을 fetch해서 읽는 구조를 별도로 만든다.
// 앱 시작 전 실행
async function initConfig() {
const res = await fetch('/config.json')
window.APP_CONFIG = await res.json()
}
await initConfig()
// 사용 — 런타임 우선, 없으면 빌드타임 fallback
const apiUrl =
window.APP_CONFIG?.API_URL ??
import.meta.env.VITE_API_URL
/config.json을 서버에서 직접 수정하면 재빌드 없이 반영된다. 다만 async 초기화 처리가 필요하고 구조가 복잡해지므로, 하나의 빌드로 여러 환경에 배포하거나 배포 없이 값을 자주 바꿔야 하는 경우에만 쓴다.
📌 혼합형 — 실무에서 가장 흔한 형태
기준은 하나다.
"이 값이 노출됐을 때 문제가 생기는가?"
- 괜찮으면 → git에서 관리
- 안 되면 → 서버 파일 / 환경변수 / Secret Manager
# git에 올라가는 것 — 공개 가능한 값
server:
port: 8080
feature:
cache:
enabled: true
# 서버에만 있는 것 — 민감한 값
# /opt/config/secrets.yml
database:
password: super_secret
api:
jwt-secret: xyz789
# 실행 시 두 파일을 합쳐서 읽음
java -jar app.jar \
--spring.config.location=classpath:/application.yml,/opt/config/secrets.yml
환경변수로 주입하는 방식도 흔하다. Docker, Kubernetes 환경에서 특히 많이 쓴다.
export DB_PASSWORD="super_secret"
python app.py
클라우드 환경이라면 파일이나 환경변수 대신 Secret Manager API를 직접 호출하기도 한다.
값의 종류 관리 방식
| 포트, 기능 플래그, 기본 옵션 | git |
| DB 비밀번호, API 시크릿 | 서버 파일 또는 환경변수 |
| JWT 시크릿, 인증 키 | 환경변수 또는 Secret Manager |
| 프론트 API URL | build-time env (공개 가능한 값만) |
| 프론트 시크릿 | 절대 프론트에 포함하지 않음 |
⚠️ 헷갈리기 쉬운 포인트
"서버에서 config 수정했는데 배포 후 왜 원복됐지?" 방식 2(코드와 함께 배포)라면 당연하다. 서버 직접 수정이 아니라 git → CI/CD 흐름으로 변경해야 한다.
"stg용으로 빌드했는데 prod에 배포하면?" 프론트라면 stg 값이 번들에 박힌 채 prod에 올라간다. 빌드 환경과 배포 환경은 반드시 일치시켜야 한다.
"프론트 env에 시크릿 키 넣어도 되나?" 절대 안 된다. 번들에 포함되어 브라우저에서 노출된다.
📋 정리
항목 내용
| 언어 차이인가 | 아니다. Java/Python/Node 모두 두 방식 가능 |
| 외부 Config 분리 | 서버가 상태 관리. 배포해도 config 유지 |
| 코드와 함께 배포 | Git이 상태 관리. 배포 시 덮어쓰기 |
| 프론트 CSR | 빌드타임에 값 확정. 배포 후 env 수정은 효과 없음 |
| 혼합형 | 공개 설정은 git, 민감 정보는 외부 분리 |