@extends('layouts.app') @section('title', '.env 관리 정책') @push('styles') @endpush @section('content')
SAM 프로젝트의 환경 변수 관리 — 열쇠 고리처럼 서비스를 여는 비밀 설정 파일
.env 파일은 열쇠 고리와 같다. 데이터베이스, 메일, AI, 푸시 알림 등 여러 서비스를 여는 열쇠(비밀번호, API 키)를 한 곳에 모아둔 파일이다.
왜 설정을 코드에서 분리하는가?
보안
API 키, 비밀번호를 코드에 직접 적으면 Git에 올라가서 유출된다. .env는 Git에 올리지 않는다.
환경별 차이
로컬에서는 DB가 Docker 컨테이너, 서버에서는 localhost. 코드는 같은데 설정만 다르다.
비유: 양식과 실제 서류
.env.example은 빈 양식지다. 어떤 항목을 채워야 하는지 알려주지만 실제 값은 비어 있다.
.env는 작성 완료된 서류다. 실제 비밀번호와 API 키가 들어 있다.
.env.example (양식)
GEMINI_API_KEY=
DB_PASSWORD=sampass
INTERNAL_EXCHANGE_SECRET=
Git에 포함 (공유용)
.env (실제)
GEMINI_API_KEY=AIzaSy...
DB_PASSWORD=Pr0d_S3cur3!
INTERNAL_EXCHANGE_SECRET=abc123...
Git에 절대 포함 금지!
SAM은 MNG, API, React 3개 프로젝트로 구성되며, 각 프로젝트가 독립된 .env 파일을 보유한다. 공유 DB를 사용하지만 환경 변수는 각자 관리한다.
APP
이름, 환경, URL
Database
DB 접속 정보
Session
세션 드라이버, 수명
SMTP 서버 설정
SAM API 연동
API_BASE_URL, 내부통신키
Google AI
Gemini, Vertex, GCS
Claude AI
CLAUDE_API_KEY
FCM
Firebase 푸시 알림
Notion
MNG 전용
기상청 API
MNG 전용
보라 = 양쪽 공유 · 파랑 = MNG 전용
Slack 로깅
API 전용
Swagger
API 문서 설정
Sanctum
토큰 만료 설정
Legacy DB
5130 DB 접속
바로빌
세금계산서 SOAP
주황 = API 전용
비유: 현장 상관의 즉각 명령
군대에서 기본 명령서(.env)가 있지만, 현장 상관(docker-compose)이 "이건 이렇게 해!"라고 하면 그게 우선이다.
Docker 환경에서는 docker-compose.yml의 environment 설정이 .env보다 강하다.
docker-compose.yml environment:
최우선. 컨테이너 시작 시 직접 주입된다.
.env 파일
프로젝트 루트의 환경 변수 파일.
.env.example
기본값 참고용. 실제 적용되지 않는다.
API 프로젝트의 DB_HOST
DB_HOST=127.0.0.1
무시됨
DB_HOST=sam-mysql-1
적용됨
Docker에서 Override하는 5개 변수:
DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD
React 프로젝트
NEXT_PUBLIC_API_URL=https://api.sam.kr
NEXT_PUBLIC_API_KEY=42Jfwc6E...
NODE_ENV=development
React는 .env 파일 없이 docker-compose에서만 설정한다.
비유: 한 쌍의 자물쇠
MNG와 API는 서로 HTTP로 통신한다. INTERNAL_EXCHANGE_SECRET이 양쪽에서 다르면 HMAC 인증이 실패하여 통신이 불가능하다. 자물쇠와 열쇠가 맞지 않는 것과 같다.
| 환경 변수 | MNG 경로 | API 경로 |
|---|---|---|
| GOOGLE_APPLICATION_CREDENTIALS | /var/www/sales/apikey/google_sa.json | /var/www/mng/apikey/google_sa.json |
| FCM_SA_PATH | secrets/firebase-service-account.json | secrets/codebridge-x-firebase-sa.json |
동일한 Google 서비스 계정 파일이지만 컨테이너마다 마운트 경로가 다르다. 값을 복사하면 안 되고 각 프로젝트의 실제 경로를 사용해야 한다.
| 환경 변수 | 로컬 (Docker) | 서버 (운영) |
|---|---|---|
| APP_ENV | local |
production |
| APP_DEBUG | true |
false |
| DB_HOST | sam-mysql-1 (컨테이너명) |
127.0.0.1 (localhost) |
| DB_PASSWORD | sampass (개발용) |
●●●●●●● (강력한 비밀번호) |
운영 서버에서 APP_DEBUG=true이면?
DB 비밀번호 노출
에러 발생 시 .env 값이
브라우저에 표시된다
API 키 유출
스택 트레이스에 환경 변수
전체가 포함될 수 있다
서버 경로 노출
파일 구조, 프레임워크 버전 등
공격에 필요한 정보가 드러난다
Laravel은 성능을 위해 .env 값을 캐시에 저장해둔다.
.env를 수정해도 php artisan config:clear를 실행하지 않으면 이전 값이 계속 사용된다.
로컬 (Docker) 환경
docker exec sam-mng-1 php artisan config:clear
docker exec sam-api-1 php artisan config:clear
서버 환경
cd /home/webservice/mng && php artisan config:clear
cd /home/webservice/api && php artisan config:clear
.env.example을 .env로 복사
cp .env.example .env
APP_KEY 생성
docker exec sam-mng-1 php artisan key:generate
DB 접속 정보 확인
Docker가 Override하므로 .env 기본값으로 충분하다
공유 API 키 동기화
GEMINI_API_KEY, INTERNAL_EXCHANGE_SECRET 등을 팀장에게 받아 설정
캐시 초기화
docker exec sam-mng-1 php artisan config:clear
.env를 Git에 커밋
비밀번호, API 키가 저장소에 영구 저장된다. .gitignore에 .env가 포함되어 있는지 확인한다.
운영 서버에서 APP_DEBUG=true
에러 발생 시 모든 환경 변수가 브라우저에 노출된다.
EXCHANGE_SECRET 불일치
MNG와 API의 INTERNAL_EXCHANGE_SECRET이 다르면 서버 간 통신이 전부 실패한다.
운영 서버에서 APP_KEY 재생성
기존 세션, 암호화된 데이터가 모두 복호화 불가능해진다.
config:clear 안 하고 "안 되요"
.env 수정 후 캐시를 안 지우면 이전 설정이 계속 적용된다.
운영 키를 로컬에 복사
로컬에서 운영 DB에 접속하면 실수로 데이터를 변경할 수 있다.
MNG → API 연동 실패 (401/403)
해결
양쪽 INTERNAL_EXCHANGE_SECRET 값 일치 확인 → config:clear
AI 기능 (Gemini/Claude) 동작 안 함
해결
해당 프로젝트 .env의 GEMINI_API_KEY 또는 CLAUDE_API_KEY 값 확인 → config:clear
DB 접속 오류 (Connection refused)
해결
Docker: DB_HOST=sam-mysql-1 / 서버: DB_HOST=127.0.0.1 확인. Docker에서는 compose가 override하므로 .env 값은 무관.
Google 서비스 계정 파일 오류
해결
GOOGLE_APPLICATION_CREDENTIALS 경로가 해당 컨테이너의 마운트 경로와 일치하는지 확인.
MNG와 API의 경로가 다르다.
.env 수정했는데 설정이 안 바뀜
해결
php artisan config:clear 실행. Docker의 override 변수인지도 확인 (compose가 .env보다 우선).
신규 환경 변수 추가 시 절차
.env.example에 키=기본값 추가 (다른 개발자를 위한 문서 역할).env에 실제 값 설정config/services.php 등에서 env('NEW_KEY')로 참조