배포 환경 변수 주입은 어떻게 풀어낼까?

2026. 4. 30. 12:12·Infra

1. 개요

배포 환경에서는 DB 계정, JWT 시크릿 같은 민감한 데이터와 CORS 허용 도메인 같은 환경별 값이 필요하다.
값을 코드에 하드코딩하면 유출 리스크가 커지고, 환경(dev/stage/prod)별로 빌드 아티팩트를 다시 만들어야 한다.
값을 서버에 수동으로 입력하면 휴먼 에러가 늘고, 멀티 서버 환경에서는 서버마다 값이 달라지는 사고가 난다.
또, 컨테이너가 재기동되면 값이 사라지거나(임시 export), 운영자가 기억에 의존하는 문제가 생긴다.
배포 자동화가 있는 경우에도 어디에서 어떤 파일/변수로 주입되는지가 불명확하면 장애 시 원인 파악이 어렵다.

즉, 배포 환경변수 주입은 운영에서 재현 불가/복구 지연/보안 유출로 이어지는 중요한 문제다.

 

이제 어떤 방식들로 해결할 수 있는지 알아보자.

 

2. 해결 방법

  • A. Docker Compose --env-file로 주입한다: 배포 스크립트가 .env 파일 경로를 강제하고 모든 compose 실행에 동일 파일을 사용한다.
  • B. 서버에 영구 환경변수(export)로 주입한다: systemd/프로파일에 환경변수를 저장하고 컨테이너는 이를 상속받는다.
  • C. Secret Manager로 주입한다: AWS SSM/Secrets Manager, Vault 같은 저장소에서 런타임에 받아온다.

 

A. Docker Compose --env-file로 주입한다

배포 스크립트가 ENV_FILE을 받아 docker compose --env-file "$ENV_FILE" 형태로 실행한다.
docker-compose.yml에서는 ${VAR} 형태로 값을 참조하고, 실제 값은 env 파일에만 존재한다.

장점

  • 운영 관점에서 “어떤 값이 쓰였는지”가 env 파일 한 장으로 고정되어 재현성이 좋다.
  • 비용 관점에서 별도 매니지드 서비스 없이 Docker/Compose만으로 구성 가능하다.
  • 복잡도 관점에서 애플리케이션 코드를 수정하지 않고 배포 레이어에서 해결 가능하다.

단점

  • env 파일이 서버 디스크에 남으면 유출 가능성이 있다.
  • 다수 서버/다수 환경으로 늘어나면 env 파일 배포/회수 전략이 필요하다.

 

B. 서버에 영구 환경변수(export)로 주입한다

서버의 쉘 프로파일, systemd unit, 혹은 CI가 원격으로 export KEY=VALUE를 등록하고 컨테이너가 이를 사용하게 한다.

장점

  • 운영 관점에서 파일을 따로 들고 다니지 않아도 된다.
  • 비용 관점에서 추가 서비스가 필요 없다.

단점

  • 배포 시점에 어떤 값이 적용됐는지 확인하기 어려워 장애 시 원인 파악이 힘들다.
  • 배포 중 다른 세션/프로세스가 다른 값을 참조하는 등 일관성 사고가 생기기 쉽다.

 

C. Secret Manager로 주입한다

시크릿을 중앙 저장소에 두고, 런타임/배포 시점에 API로 가져와 주입한다.

장점

  • 리스크 관점에서 접근 제어/감사 로그/자동 회전 같은 보안 기능을 쉽게 확보할 수 있다.
  • 확장성 관점에서 환경/서비스가 늘어나도 중앙에서 관리 가능하다.

단점

  • 비용 관점에서 매니지드 비용 또는 운영 비용이 증가한다.
  • 복잡도 관점에서 네트워크/권한/캐시/장애 시 fallback 등 고려할 요소가 늘어난다.

 

3. 도커 컴포즈 --env-file 주입을 선택한 이유

선택한 방식은 A. Docker Compose --env-file로 주입하는 방식이다.
현재 배포는 Docker Compose 기반이며, 롤링 배포 중에도 모든 슬롯이 동일한 설정을 써야 한다는 목표가 있다.
또한, 개인 프로젝트의 리소스/운영 제약상 Secret Manager를 도입하기보다, 배포 레이어에서 env 파일로 주입하는 편이 안정적이다.
트레이드오프로 env 파일 유출 리스크와 파일 배포 관리 부담을 감수해야한다.
이를 /tmp 에 env 파일을 복사하여 사용하다가 배포 종료 시 정리 같은 수명 관리로 리스크를 낮췄다.

 

4. 구현 방법

4-1. 순서

  • ENV_FILE 경로를 배포 스크립트 입력으로 받는다
  • 배포 스크립트가 ENV_FILE 존재를 검사하고, 모든 docker compose 호출에 --env-file "$ENV_FILE"을 붙인다
  • docker-compose.infra.yml(MySQL/Mongo/Redis)와 docker-compose.rolling.yml(app/web), docker-compose.router.yml(nginx)가 같은 env 파일을 공유한다
  • 백엔드는 ${JWT_SECRET}, ${CORS_ALLOWED_ORIGINS} 같은 값을 컨테이너 환경변수로 받는다
  • 헬스체크(/health)로 롤링 배포 중 “준비됨” 상태를 판단한다

4-2. 코드 분석

아래는 env 파일 주입하는 스크립트(deploy/deploy.sh)이다.

ENV_FILE="${ENV_FILE:-/tmp/.funchat.env}"
require_file "$ENV_FILE" "ENV_FILE"

router_compose() {
  docker compose --env-file "$ENV_FILE" -f docker-compose.router.yml "$@"
}

rolling_compose() {
  docker compose --env-file "$ENV_FILE" -p funchat-rolling -f docker-compose.rolling.yml "$@"
}

 

require_file을 통해 ENV_FILE이 없으면 배포를 중단한다.

router_compose, rolling_compose로 ENV_FILE을 주입하면서 도커 컴포즈를 실행한다.

 

아래는 ${VAR}를 받아 컨테이너 환경변수로 전달하는 부분(deploy/docker-compose.rolling.yml)이다.

environment:
  SERVER_PORT: '8081'
  SPRING_DATASOURCE_URL: jdbc:mysql://funchat-mysqldb:3306/funchat?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
  SPRING_DATA_REDIS_HOST: funchat-redis
  SPRING_DATA_MONGODB_HOST: funchat-mongodb
  USER_ID: ${USER_ID}
  USER_PW: ${USER_PW}
  ROOT_PW: ${ROOT_PW}
  JWT_SECRET: ${JWT_SECRET}
  ACCESS_EXPIRATION: ${ACCESS_EXPIRATION}
  REFRESH_EXPIRATION: ${REFRESH_EXPIRATION}
  CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS}


백엔드 컨테이너가 런타임 설정을 환경변수로만 받게 만들고, 민감 값은 ${VAR}로 외부화한다.
Compose가 env 파일을 읽어 ${USER_ID}, ${JWT_SECRET} 같은 토큰을 치환하고 컨테이너 환경변수로 주입한다.

 

 

마지막으로 롤링 배포에서 주입된 값이 정상인지를 빠르게 확인하는 장치가 필요하다.
이 프로젝트는 /health 엔드포인트를 두고, compose 헬스체크가 이를 호출한다.

healthcheck:
  test: ['CMD-SHELL', "wget -q -O- http://localhost:8081/health >/dev/null"]
  interval: 5s
  timeout: 2s
  retries: 30


롤링 중 새 슬롯이 준비됨 상태가 될 때까지 기다리게 하여, 잘못된 환경변수 주입으로 인한 즉시 장애를 배포 단계에서 잡는다.
컨테이너 기동 → /health 응답 성공까지 반복 → 성공 시에만 다음 단계(업스트림 반영/스모크 테스트)로 진행한다.
환경변수 오타/누락으로 애플리케이션이 부팅은 되었지만 DB/Redis/Mongo 연결에서 실패하는 경우도 있어 헬스체크로 잡아낸다.

 

5. 마무리

Docker Compose --env-file 주입 적용하고 난 후 장점

  • 배포 시점 설정이 env 파일 한 장으로 고정되어 복잡도가 낮다.
  • 슬롯 기반 롤링 배포에서도 같은 입력(--env-file)을 강제하여 일관성이 높아진다.
  • 이미지 재빌드 없이 환경변수 값을 교체할 수 있어 운영 부담이 줄어든다.

개선 사항

  • env 파일을 /tmp로 두더라도 노출 가능성이 있으므로 Docker socket 및 파일 권한을 엄격히 해야 한다.
  • CI(Jenkins)에서 env 파일을 만들 때 마스킹/로그 노출 방지 규칙을 추가해야 한다.
  • 장기적으로는 Secret Manager 도입으로 값 회전과 감사 로그를 확보할 것이다.
  • 프론트엔드의 빌드 타임과 런타임 설정의 경계를 정리하여, 도메인/경로 같은 값이 어디에서 주입되는지 파악해야한다.

'Infra' 카테고리의 다른 글

무중단 배포 & 로드밸런싱 전략 적용  (0) 2026.04.28
SonarQube + Jenkins + GitHub CI/CD 연동기  (0) 2026.02.25
Jenkins와 Docker Compose를 이용한 Spring Boot CI/CD 구축기(feat. 홈서버)  (0) 2026.01.29
N8N 홈서버 구축 회고(2) - 도커 설정 및 트러블 슈팅  (0) 2026.01.27
'Infra' 카테고리의 다른 글
  • 무중단 배포 & 로드밸런싱 전략 적용
  • SonarQube + Jenkins + GitHub CI/CD 연동기
  • Jenkins와 Docker Compose를 이용한 Spring Boot CI/CD 구축기(feat. 홈서버)
  • N8N 홈서버 구축 회고(2) - 도커 설정 및 트러블 슈팅
뽀루피
뽀루피
  • 뽀루피
    뽀루피의 개발스토리
    뽀루피
  • 전체
    오늘
    어제
    • 분류 전체보기 (83)
      • 프로젝트 (3)
      • Java (32)
        • JPA (4)
        • Spring (8)
      • FrontEnd (4)
        • JavaScript (1)
        • Flutter (3)
      • DB (4)
        • SQL (4)
      • 알고리즘 (10)
      • Git (3)
      • SSAFY (0)
      • Figma (1)
      • Notion (0)
      • Jira (0)
      • 기획 (0)
      • 네트워크 (10)
      • 우아한테크코스 (2)
      • Infra (5)
        • AWS (0)
        • Docker (3)
      • AI (1)
      • 자격증 (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    네트워크 기초
    트랜잭션
    JPA
    n8n
    스프링
    Jenkins
    웹소켓
    Docker
    모던 자바 인 액션
    redis streams
    백준
    SQL
    네트워크
    자바
    fetch join
    Stomp
    cloudflare
    알고리즘
    크래프트맨 멘탈리티
    java
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
뽀루피
배포 환경 변수 주입은 어떻게 풀어낼까?
상단으로

티스토리툴바